From 995d924bfc92c2d165d5edf61503f8aca90582cb Mon Sep 17 00:00:00 2001 From: sorooj Date: Mon, 24 Feb 2025 11:38:55 +0500 Subject: [PATCH 001/100] added cron job and webhook functionality --- .gitignore | 4 + apiRouter/apiRouter.js | 9 + apiRouter/token.js | 8 + apiRouter/webhook.js | 12 + config/tokens.js | 0 controllers/tokenController.js | 20 + controllers/webhookController.js | 47 ++ jobs/postTweetJob.js | 37 + package-lock.json | 1182 ++++++++++++++++++++++++++++++ package.json | 20 + schedule.json | 4 + server.js | 33 + services/tokens.json | 1 + services/twitterService.js | 217 ++++++ 14 files changed, 1594 insertions(+) create mode 100644 .gitignore create mode 100644 apiRouter/apiRouter.js create mode 100644 apiRouter/token.js create mode 100644 apiRouter/webhook.js create mode 100644 config/tokens.js create mode 100644 controllers/tokenController.js create mode 100644 controllers/webhookController.js create mode 100644 jobs/postTweetJob.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 schedule.json create mode 100644 server.js create mode 100644 services/tokens.json create mode 100644 services/twitterService.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4949cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +#node_modules +node_modules/ +#env +.env diff --git a/apiRouter/apiRouter.js b/apiRouter/apiRouter.js new file mode 100644 index 0000000..70de506 --- /dev/null +++ b/apiRouter/apiRouter.js @@ -0,0 +1,9 @@ +const express = require("express"); +const app = express(); +const webhookRoutes = require("./webhook"); +const tokensRoutes = require("./token"); + +app.use("/webhook", webhookRoutes); +app.use("/tokens", tokensRoutes); + +module.exports = app; diff --git a/apiRouter/token.js b/apiRouter/token.js new file mode 100644 index 0000000..a2c9348 --- /dev/null +++ b/apiRouter/token.js @@ -0,0 +1,8 @@ +const express = require("express"); +const { loadTokens } = require("../controllers/tokenController"); + +const router = express.Router(); + +router.post("/", loadTokens); + +module.exports = router; diff --git a/apiRouter/webhook.js b/apiRouter/webhook.js new file mode 100644 index 0000000..838d179 --- /dev/null +++ b/apiRouter/webhook.js @@ -0,0 +1,12 @@ +const express = require("express"); +const { + registerWebhook, + tweetWebhook, +} = require("../controllers/webhookController"); + +const router = express.Router(); + +router.post("/register", registerWebhook); +router.post("/", tweetWebhook); + +module.exports = router; diff --git a/config/tokens.js b/config/tokens.js new file mode 100644 index 0000000..e69de29 diff --git a/controllers/tokenController.js b/controllers/tokenController.js new file mode 100644 index 0000000..c022d01 --- /dev/null +++ b/controllers/tokenController.js @@ -0,0 +1,20 @@ +const { saveTokens } = require("../services/twitterService"); + +const loadTokens = async (req, res, next) => { + try { + const encryptionKey = process.env.ENCRYPTION_KEY; + const { accessToken, refreshToken } = req.body; + if (!accessToken || !refreshToken) { + throw new Error("accessToken & refreshToken are required"); + } + saveTokens(accessToken, refreshToken, encryptionKey); + res.send({ + message: "tokens saved", + }); + } catch (err) { + console.log(`error registering webhook ${err}`); + next(err); + } +}; + +module.exports = { loadTokens }; diff --git a/controllers/webhookController.js b/controllers/webhookController.js new file mode 100644 index 0000000..6bf2b17 --- /dev/null +++ b/controllers/webhookController.js @@ -0,0 +1,47 @@ +const axios = require("axios"); +const { uploadTwitterPostTweet } = require("../services/twitterService"); + +const registerWebhook = async (req, res, next) => { + try { + const { url } = req.body; + if (!url) { + throw new Error("url is required"); + } + const response = await axios.post( + `${process.env.WEB_URL}/webhook-subscription/register`, + { + url, + }, + { + headers: { + "api-key": "4ca83cba-a40a-43fd-a215-d9b45f5aed9d", + }, + } + ); + // return response.data; + res.send({ + message: "webhook registered successfully", + response: response.data, + }); + } catch (err) { + console.log(`error registering webhook ${err}`); + next(err); + } +}; + +const tweetWebhook = async (req, res, next) => { + try { + const { tweet } = req.body; + if (!tweet) { + throw new Error("tweet is required"); + } + + await uploadTwitterPostTweet(tweet); + res.send({ message: "tweeted successfully", tweet }); + } catch (err) { + console.log(`error posting tweet using webhook ${err}`); + next(err); + } +}; + +module.exports = { registerWebhook, tweetWebhook }; diff --git a/jobs/postTweetJob.js b/jobs/postTweetJob.js new file mode 100644 index 0000000..8b2196a --- /dev/null +++ b/jobs/postTweetJob.js @@ -0,0 +1,37 @@ +const cron = require("node-cron"); +const path = require("path"); +const fs = require("fs"); +const { generateAndPostTweet } = require("../services/twitterService"); + +const configPath = path.join(__dirname, "../schedule.json"); + +function loadConfig() { + try { + const data = fs.readFileSync(configPath, "utf8"); + return JSON.parse(data); + } catch (error) { + console.error("Error reading schedule config:", error); + return {}; + } +} + +function scheduleTweets() { + const config = loadConfig(); + for (const time in config) { + const prompt = config[time]; + const [hour, minute] = time.split(":"); + + cron.schedule( + `${minute} ${hour} * * *`, + () => { + console.log(`Running scheduled tweet for UTC time: ${time}`); + generateAndPostTweet(prompt); + }, + { + timezone: "UTC", + } + ); + } +} + +module.exports = { scheduleTweets }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b680a8d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1182 @@ +{ + "name": "twitter-ai-agent", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "twitter-ai-agent", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.7.9", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "node-cron": "^3.0.3", + "nodemon": "^3.1.9" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ac8fc26 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "twitter-ai-agent", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "nodemon server.js", + "start": "node server.js" + }, + "author": "soroojshehryar01@gmail.com", + "license": "ISC", + "description": "", + "dependencies": { + "axios": "^1.7.9", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "node-cron": "^3.0.3", + "nodemon": "^3.1.9" + } +} diff --git a/schedule.json b/schedule.json new file mode 100644 index 0000000..566fe54 --- /dev/null +++ b/schedule.json @@ -0,0 +1,4 @@ +{ + "12:16": "crypto downfall", + "07:11": "blockchain effects" +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..272d069 --- /dev/null +++ b/server.js @@ -0,0 +1,33 @@ +require("dotenv").config(); +const express = require("express"); +const { scheduleTweets } = require("./jobs/postTweetJob"); +const apiRoutes = require("./apiRouter/apiRouter"); + +const app = express(); +const port = process.env.PORT || 8000; + +app.use(express.json()); + +// Default Route +app.get("/", (req, res) => { + res.send("Hi, this is Twitter AI Agent developed by ChainGPT"); +}); + +app.use("/api", apiRoutes); + +scheduleTweets(); +console.log("Tweet scheduler started."); + +// Global Error Handling Middleware +app.use((err, req, res, next) => { + console.error("Global Error Handler:", err.message); + + res.status(err.status || 500).json({ + message: err.message || "Internal Server Error", + status: err.status || 500, + }); +}); + +app.listen(port, () => { + console.log(`๐Ÿš€ Twitter AI Agent listening on port ${port}`); +}); diff --git a/services/tokens.json b/services/tokens.json new file mode 100644 index 0000000..f7c47e9 --- /dev/null +++ b/services/tokens.json @@ -0,0 +1 @@ +{"accessToken":"dca1193f39ebc9d622d5434cc6f05540:ebf341eaa9e0bf992d1ad6a264bd3ee4c3e1f4ee36156eb10007d5565a2b675bd604efcee4980f99e616e53d5f477cb332ecfbf6c1b39e2ba8f6bef4b0405abbdc7bb95134d602688eb6ef2a2296cbe8c8c232c4f69e8130329221","refreshToken":"1f2c2d88fa74dd1aa2e3fad3cc635883:ea97598ca984bf953414f79f71bc63cdc485cad8064a548f0006d16c58347f1be43e94bce89a07c3d623ad5b482c5e9002efc1c6ebb3c36fa9f687c4b0405abbdc7bb95134d602688eb6ef2a21a8e1e8c8c232cbf69d8d30329221"} \ No newline at end of file diff --git a/services/twitterService.js b/services/twitterService.js new file mode 100644 index 0000000..0e65c7f --- /dev/null +++ b/services/twitterService.js @@ -0,0 +1,217 @@ +const axios = require("axios"); +const crypto = require("crypto"); +const fs = require("fs"); +const path = require("path"); + +require("dotenv").config(); + +const clientId = process.env.CLIENT_ID; +const clientSecret = process.env.CLIENT_SECRET; +const encryptionKey = process.env.ENCRYPTION_KEY; + +// Encryption settings +const algorithm = "aes-256-gcm"; // Authenticated encryption +const keyLength = 32; // 256-bit key + +// Use a constant salt and IV from environment variables +const salt = Buffer.from(process.env.ENCRYPTION_SALT, "hex"); +const iv = Buffer.from(process.env.IV, "hex"); + +// Derive a secure key from the encryption key in the environment +function deriveKey(encryptionKey) { + return crypto.scryptSync(encryptionKey, salt, keyLength); +} + +// Encrypt data +function encrypt(text, encryptionKey) { + const key = deriveKey(encryptionKey); + const cipher = crypto.createCipheriv(algorithm, key, iv); + + let encrypted = cipher.update(text, "utf8", "hex"); + encrypted += cipher.final("hex"); + + const authTag = cipher.getAuthTag().toString("hex"); + + return `${authTag}:${encrypted}`; // IV is constant, no need to include it each time +} + +// Decrypt data +function decrypt(encryptedText, encryptionKey) { + const key = deriveKey(encryptionKey); + const [authTagHex, encrypted] = encryptedText.split(":"); + const authTag = Buffer.from(authTagHex, "hex"); + + const decipher = crypto.createDecipheriv(algorithm, key, iv); + decipher.setAuthTag(authTag); + + let decrypted = decipher.update(encrypted, "hex", "utf8"); + decrypted += decipher.final("utf8"); + + return decrypted; +} + +// Save tokens to a file +function saveTokens(accessToken, refreshToken, encryptionKey) { + const tokens = { + accessToken: encrypt(accessToken, encryptionKey), + refreshToken: encrypt(refreshToken, encryptionKey), + }; + fs.writeFileSync(path.join(__dirname, "tokens.json"), JSON.stringify(tokens)); +} + +// Load tokens from a file +function loadTokens(encryptionKey) { + try { + const tokens = JSON.parse( + fs.readFileSync(path.join(__dirname, "tokens.json")) + ); + const accessToken = decrypt(tokens.accessToken, encryptionKey); + const refreshToken = decrypt(tokens.refreshToken, encryptionKey); + return { accessToken, refreshToken }; + } catch (error) { + console.error("Error loading tokens:", error.message); + return null; + } +} + +const getAcesstokenOftwitterExtra = async () => { + let { accessToken, refreshToken } = loadTokens(encryptionKey); + + if (!accessToken) { + throw new Error("No active Twitter authorization found."); + } + + try { + const response = await axios.get("https://api.twitter.com/2/users/me", { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + return accessToken; + } catch (error) { + console.error( + "๐Ÿš€ Twitter Token Check Error:", + error.response?.status || error.message + ); + + if (error.response?.status === 401) { + try { + const { newAccessToken, newRefreshToken } = await refreshAccessToken( + refreshToken + ); + saveTokens(newAccessToken, newRefreshToken, encryptionKey); + + return newAccessToken; + } catch (refreshError) { + console.error("Failed to refresh token:", refreshError.message); + throw refreshError; + } + } + + throw error; + } +}; + +const refreshAccessToken = async (refreshToken) => { + try { + console.log("refreshing access token"); + const base64Credentials = Buffer.from( + `${clientId}:${clientSecret}` + ).toString("base64"); + + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + new URLSearchParams({ + grant_type: "refresh_token", + refresh_token: refreshToken, + }).toString(), + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${base64Credentials}`, + }, + } + ); + + console.log("refreshed access token"); + + return { + newAccessToken: response.data.access_token, + newRefreshToken: response.data.refresh_token, + }; + } catch (error) { + console.error( + "Error refreshing access token:", + error.response?.data || error.message + ); + throw error; + } +}; + +const getTextForTweet = async (prompt) => { + try { + const response = await axios.post( + `${process.env.WEB_URL}/tweet-generator`, + { + prompt, + }, + { + headers: { + "api-key": "4ca83cba-a40a-43fd-a215-d9b45f5aed9d", + }, + } + ); + return response.data.tweet; + } catch (error) { + console.error("Error fetching tweet text:", error.message); + throw new Error(`Error generating content: ${error.message}`); + } +}; + +const postTweet = async (accessToken, message) => { + const url = "https://api.twitter.com/2/tweets"; + + const headers = { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }; + + try { + const response = await axios.post(url, { text: message }, { headers }); + return response.data; + } catch (error) { + console.error( + "Error posting tweet:", + error.response?.data || error.message + ); + throw new Error(`Failed to post tweet: ${error.message}`); + } +}; + +const uploadTwitterPostTweet = async (message) => { + console.log("๐Ÿš€ Posting Tweet:", message); + + let accessToken; + try { + accessToken = await getAcesstokenOftwitterExtra(); + await postTweet(accessToken, message); + } catch (error) { + console.error("Failed to upload tweet:", error.message); + throw error; + } +}; + +const generateAndPostTweet = async (prompt) => { + try { + if (!prompt) { + throw new Error("prompt is required"); + } + + let tweet = await getTextForTweet(prompt); + tweet = tweet.length > 270 ? tweet.substring(0, 270) + "..." : tweet; + await uploadTwitterPostTweet(tweet); + } catch (error) { + console.error("error in posting tweet", error); + } +}; + +module.exports = { generateAndPostTweet, uploadTwitterPostTweet, saveTokens }; From f26597439d5e8e0e652d2ed606c9e7a2f9f12b6f Mon Sep 17 00:00:00 2001 From: sorooj Date: Mon, 24 Feb 2025 14:27:36 +0500 Subject: [PATCH 002/100] updated read me --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4809b64..e483a56 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ -# Twitter_AI_Agent \ No newline at end of file +Project Setup Guide +Prerequisites +Node.js (Latest LTS version recommended) +npm (comes with Node.js) + +Installation Steps +Install Dependencies: +npm install + +Start the Project: +npm run start + +Configure Environment Variables: +Create a .env file in the project's root directory. +Add the following fields: +CLIENT_ID= +CLIENT_SECRET= +ENCRYPTION_KEY= +IV= +ENCRYPTION_SALT= +WEB_URL=https://appapi.chaingpt.dev + +Run the Project: +After setting up the .env file, restart the project to load the environment variables. +Your project should now be operational, automating tweet generation and posting based on your configurations. From f2bd9ee485450d8172e344ee9268d7f178d30615 Mon Sep 17 00:00:00 2001 From: sorooj Date: Mon, 24 Feb 2025 16:17:04 +0500 Subject: [PATCH 003/100] updated read me --- README.md | 145 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e483a56..4fc8f33 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,122 @@ -Project Setup Guide +Project Overview +This open-source project provides two key features for automated tweet generation and posting +Scheduled Tweeting with Cron Jobs +Users can define a schedule file containing prompts and specific times for tweet generation. +A cron job will process the schedule, generate a tweet based on the provided prompt, and automatically publish it. +Automated Tweeting via ChainGPT Webhooks +Users can register their webhook with the ChainGPT portal and subscribe to specific news categories. +Whenever ChainGPT publishes news in a subscribed category, the project will receive a relevant tweet and post it automatically. +Requirements +A ChainGPT API key is required for authentication and access to the service. +Each tweet generation consumes 1 credit. +How to generate API key +To use this project, you need a ChainGPT API key. Follow these steps to generate one: +Access the API Dashboard +Go to ChainGPT API Dashboard. +Connect your crypto wallet to authenticate. +Create an API Key +Click on "Create New Key". +Copy and securely store the secret phrase, as it will be required for authentication. +Purchase Credits +Visit ChainGPT Credits. +Buy credits as each tweet generation will consume 1 credit. +Once you have your API key and credits, you can start using the project for automated tweet generation! + +Working Steps +Before running the project, follow these steps to set up and execute the workflow. +Load Twitter Access Tokens +Before generating tweets, you need to load your Twitter Access Token and Refresh Token into the project. Use the following API call: +Request: +POST {projectUrl}/api/tokens/ +Body: +{ +"accessToken": "", +"refreshToken": "" +} + +Once this is done, the project will be able to authenticate and post tweets on your behalf. + +Flow 1: Tweet Using Cron Job +In this workflow, tweets are generated based on predefined prompts and times stored in a JSON file. +Step 2: Define Your Schedule +Create a schedule.json file with the following format: +{ +"14:30": "The future of AI", +"18:00": "Crypto markets are evolving" +} + +time: Must be in UTC format (HH:MM). +prompt: The text that will be used to generate the tweet. +Step 3: Run the Cron Job +The cron job will execute automatically at the specified times, generating tweets using chainGPT tweet generation feature and publishing them to Twitter. + +Flow 2: Subscribe to ChainGPT Categories +In this workflow, users can subscribe to specific categories available on ChainGPT. When a news article is published in a subscribed category, the project will automatically generate a tweet and post it to Twitter. +Step 1: Get Available Categories +To see all available categories and your currently subscribed ones, use the following API: +Request: +GET https://appapi.chaingpt.dev/category-subscription/ + +Headers: +{ +"api-key": "" +} +This will return a list of all categories along with the categories you are currently subscribed to. + +Step 2: Subscribe to Categories +To subscribe to one or more categories, send a POST request with the category IDs you want to subscribe to. +Request: +POST https://appapi.chaingpt.dev/category-subscription/subscribe + +Headers: +{ +"api-key": "" +} +Body: +{ +"categoryIds": [2, 3] +} +Here, 2 and 3 correspond to category id you want to tweet about +Step 3: Register Webhook with ChainGPT +After subscribing to categories, you must register your webhook with ChainGPT. This ensures that whenever a news article is published in a subscribed category, the webhook receives the update and automatically generates a tweet. +Register Webhook +Send a POST request to your project's webhook registration route: +Request: +POST {base_url_of_your_project}/api/webhook/register +Body: +{ +"url": "{base_url_of_your_project}/api/webhook/" +} +How It Works: +This registers your webhook URL with ChainGPT. +When ChainGPT publishes news in your subscribed categories, it will send a POST request to your webhook. +The project will process the incoming tweet and post it to Twitter. +With this setup, the entire process is automated, ensuring real-time tweet updates for the latest news in your subscribed categories. + +Project Setup Guide ๐Ÿš€ Prerequisites -Node.js (Latest LTS version recommended) -npm (comes with Node.js) - -Installation Steps -Install Dependencies: -npm install - -Start the Project: -npm run start - -Configure Environment Variables: -Create a .env file in the project's root directory. -Add the following fields: -CLIENT_ID= -CLIENT_SECRET= -ENCRYPTION_KEY= -IV= -ENCRYPTION_SALT= -WEB_URL=https://appapi.chaingpt.dev - -Run the Project: -After setting up the .env file, restart the project to load the environment variables. -Your project should now be operational, automating tweet generation and posting based on your configurations. +Ensure you have the following installed on your system: +Node.js (Latest LTS recommended) +npm (Installed with Node.js) +How to Run the Project +Follow these steps to set up and start the project: + +1. Install Dependencies + npm install + +2. Start the Project + Use the following command to start the project: + npm run start + +3. Configure Environment Variables + Create a .env file in the project's root directory and add the following fields: + CLIENT_ID= + CLIENT_SECRET= + ENCRYPTION_KEY= + IV= + ENCRYPTION_SALT= + WEB_URL=https://appapi.chaingpt.dev + +4. Run the Project + Once the .env file is set up, restart the project to ensure the environment variables are loaded. + Now your project should be up and running! ๐Ÿš€ From 5cf302f5c7b30129f6e041b4a7605efc5a83c59a Mon Sep 17 00:00:00 2001 From: sorooj Date: Tue, 25 Feb 2025 10:58:42 +0500 Subject: [PATCH 004/100] added api key to env --- controllers/webhookController.js | 2 +- services/twitterService.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/webhookController.js b/controllers/webhookController.js index 6bf2b17..3d14ecd 100644 --- a/controllers/webhookController.js +++ b/controllers/webhookController.js @@ -14,7 +14,7 @@ const registerWebhook = async (req, res, next) => { }, { headers: { - "api-key": "4ca83cba-a40a-43fd-a215-d9b45f5aed9d", + "api-key": process.env.API_KEY, }, } ); diff --git a/services/twitterService.js b/services/twitterService.js index 0e65c7f..babc01c 100644 --- a/services/twitterService.js +++ b/services/twitterService.js @@ -156,7 +156,7 @@ const getTextForTweet = async (prompt) => { }, { headers: { - "api-key": "4ca83cba-a40a-43fd-a215-d9b45f5aed9d", + "api-key": process.env.API_KEY, }, } ); From 9fcc9f468141d21f1ca9b8bd3e84b767f85ce4c5 Mon Sep 17 00:00:00 2001 From: sorooj Date: Tue, 25 Feb 2025 18:31:52 +0500 Subject: [PATCH 005/100] added descriptions --- config/tokens.js | 0 controllers/tokenController.js | 1 + controllers/webhookController.js | 2 ++ jobs/postTweetJob.js | 1 + schedule.json | 2 +- services/tokens.json | 5 ++++- services/twitterService.js | 5 +++++ 7 files changed, 14 insertions(+), 2 deletions(-) delete mode 100644 config/tokens.js diff --git a/config/tokens.js b/config/tokens.js deleted file mode 100644 index e69de29..0000000 diff --git a/controllers/tokenController.js b/controllers/tokenController.js index c022d01..f5a212a 100644 --- a/controllers/tokenController.js +++ b/controllers/tokenController.js @@ -1,5 +1,6 @@ const { saveTokens } = require("../services/twitterService"); +// api to encrypt twitter access and refresh token then load tokens into token file const loadTokens = async (req, res, next) => { try { const encryptionKey = process.env.ENCRYPTION_KEY; diff --git a/controllers/webhookController.js b/controllers/webhookController.js index 3d14ecd..86aadf8 100644 --- a/controllers/webhookController.js +++ b/controllers/webhookController.js @@ -1,6 +1,7 @@ const axios = require("axios"); const { uploadTwitterPostTweet } = require("../services/twitterService"); +//api to register webhook with chaingpt const registerWebhook = async (req, res, next) => { try { const { url } = req.body; @@ -29,6 +30,7 @@ const registerWebhook = async (req, res, next) => { } }; +// webhook to receive tweet and publish it const tweetWebhook = async (req, res, next) => { try { const { tweet } = req.body; diff --git a/jobs/postTweetJob.js b/jobs/postTweetJob.js index 8b2196a..a4644ec 100644 --- a/jobs/postTweetJob.js +++ b/jobs/postTweetJob.js @@ -15,6 +15,7 @@ function loadConfig() { } } +// cron job to read schedule.json file and schedule tweet generation and posting according to prompt function scheduleTweets() { const config = loadConfig(); for (const time in config) { diff --git a/schedule.json b/schedule.json index 566fe54..ce59246 100644 --- a/schedule.json +++ b/schedule.json @@ -1,4 +1,4 @@ { - "12:16": "crypto downfall", + "06:48": "crypto downfall", "07:11": "blockchain effects" } diff --git a/services/tokens.json b/services/tokens.json index f7c47e9..4b82263 100644 --- a/services/tokens.json +++ b/services/tokens.json @@ -1 +1,4 @@ -{"accessToken":"dca1193f39ebc9d622d5434cc6f05540:ebf341eaa9e0bf992d1ad6a264bd3ee4c3e1f4ee36156eb10007d5565a2b675bd604efcee4980f99e616e53d5f477cb332ecfbf6c1b39e2ba8f6bef4b0405abbdc7bb95134d602688eb6ef2a2296cbe8c8c232c4f69e8130329221","refreshToken":"1f2c2d88fa74dd1aa2e3fad3cc635883:ea97598ca984bf953414f79f71bc63cdc485cad8064a548f0006d16c58347f1be43e94bce89a07c3d623ad5b482c5e9002efc1c6ebb3c36fa9f687c4b0405abbdc7bb95134d602688eb6ef2a21a8e1e8c8c232cbf69d8d30329221"} \ No newline at end of file +{ + "accessToken": "", + "refreshToken": "" +} diff --git a/services/twitterService.js b/services/twitterService.js index babc01c..5787bab 100644 --- a/services/twitterService.js +++ b/services/twitterService.js @@ -74,6 +74,7 @@ function loadTokens(encryptionKey) { } } +//get access and refresh token after decrypting, it access token is expired generate new access token using refresh token const getAcesstokenOftwitterExtra = async () => { let { accessToken, refreshToken } = loadTokens(encryptionKey); @@ -111,6 +112,7 @@ const getAcesstokenOftwitterExtra = async () => { } }; +// generate new access and refresh token from refresh token const refreshAccessToken = async (refreshToken) => { try { console.log("refreshing access token"); @@ -147,6 +149,7 @@ const refreshAccessToken = async (refreshToken) => { } }; +// get text to tweet using cron scheduled job const getTextForTweet = async (prompt) => { try { const response = await axios.post( @@ -167,6 +170,7 @@ const getTextForTweet = async (prompt) => { } }; +// post tweet to twitter const postTweet = async (accessToken, message) => { const url = "https://api.twitter.com/2/tweets"; @@ -207,6 +211,7 @@ const generateAndPostTweet = async (prompt) => { } let tweet = await getTextForTweet(prompt); + // set limit according to your twitter apikey limits tweet = tweet.length > 270 ? tweet.substring(0, 270) + "..." : tweet; await uploadTwitterPostTweet(tweet); } catch (error) { From 3c8c3cfd960bea323938cb36ee8a037dc3ff3956 Mon Sep 17 00:00:00 2001 From: sorooj Date: Wed, 26 Feb 2025 15:47:28 +0500 Subject: [PATCH 006/100] updated schedule.json with default prompts --- schedule.json | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/schedule.json b/schedule.json index ce59246..1ea37bb 100644 --- a/schedule.json +++ b/schedule.json @@ -1,4 +1,20 @@ { - "06:48": "crypto downfall", - "07:11": "blockchain effects" + "00:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create fun, engaging polls for your audience to spark conversation. Provide 1 poll. it will be a poll on twitter, so please provide it in the correct template. Only provide this information: Title / Question To Ask Option #1 Option #2 Option #3 Option #4", + "0:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the latest and most important crypto news.", + "03:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at explaining complex crypto concepts and AI concepts in simple terms. Create a short, engaging tweet explaining one key blockchain or crypto concept in less than 280 characters.", + + "04:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than 280 characters. \n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread.", + + "06:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and excellent at spotting key market movements. Create a tweet (less than 280 characters) that's a meme about crypto.", + "07:30": "Write something about the market based on the fear & greed index and other indicators. What is the conclusion. Make it short, less than 280 characters.", + "09:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the latest and most important crypto news.", + "10:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the most hyped NFT collections and their volumes.", + "12:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at educating people about crypto security. Create a tweet (less than 280 characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, โ€˜Daily security tip, from your favorite AI Agentโ€™ and include a security-related emoji. Ensure the tip is distinct and doesnโ€™t repeat the style, structure, or examples from previous responses.", + "13:30": "Create a crypto market analysis report showing all the crypto related indicators that could be helpful. Start the report with : Here's a Comprehensive Analysis Report on Key Crypto Market Indicators: (Expand the tweet to view it in full ๐Ÿ‘‡)", + "15:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and excellent at spotting key market movements. Create a tweet (less than 280 characters) that's a meme about crypto.", + "16:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the latest and most important crypto news.", + "18:00": "Name 10 tokens that are trending right now. Write about it in this form: Hey fam, here are the top ten tokens trending right now ๐Ÿ‘‡ DYOR: $NAME $NAME $NAME $NAME. Top trending tokens on coinmarketcap", + "19:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet about the latest and most important crypto news.", + "21:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and excellent at spotting key market movements. Create a tweet (less than 280 characters) announcing a major crypto market change. Based on all the information , and market indicators, and news you have. Post about the most important or interesting market shift or event that is happening right now.", + "22:30": "Write something about the market based on the bitcoin vs alts vs stablecoin dominance and other indicators. What is the conclusion. Make it short, less than 280 characters, tweet style." } From 1078865eada6bef445d4058d5b0ec5c2aef9a599 Mon Sep 17 00:00:00 2001 From: maryamns <128455970+maryamns@users.noreply.github.com> Date: Tue, 4 Mar 2025 07:43:47 +0500 Subject: [PATCH 007/100] Update README.md better and readable format --- README.md | 259 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 157 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 4fc8f33..70a6a6c 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,177 @@ -Project Overview -This open-source project provides two key features for automated tweet generation and posting -Scheduled Tweeting with Cron Jobs -Users can define a schedule file containing prompts and specific times for tweet generation. -A cron job will process the schedule, generate a tweet based on the provided prompt, and automatically publish it. -Automated Tweeting via ChainGPT Webhooks -Users can register their webhook with the ChainGPT portal and subscribe to specific news categories. -Whenever ChainGPT publishes news in a subscribed category, the project will receive a relevant tweet and post it automatically. -Requirements -A ChainGPT API key is required for authentication and access to the service. -Each tweet generation consumes 1 credit. -How to generate API key -To use this project, you need a ChainGPT API key. Follow these steps to generate one: -Access the API Dashboard -Go to ChainGPT API Dashboard. -Connect your crypto wallet to authenticate. -Create an API Key -Click on "Create New Key". -Copy and securely store the secret phrase, as it will be required for authentication. -Purchase Credits -Visit ChainGPT Credits. -Buy credits as each tweet generation will consume 1 credit. -Once you have your API key and credits, you can start using the project for automated tweet generation! - -Working Steps -Before running the project, follow these steps to set up and execute the workflow. -Load Twitter Access Tokens -Before generating tweets, you need to load your Twitter Access Token and Refresh Token into the project. Use the following API call: -Request: +# ๐Ÿ“ข Automated Tweeting with ChainGPT + +## ๐Ÿš€ Project Overview +This open-source project provides two key features for automated tweet generation and posting: + +### ๐Ÿ•’ Scheduled Tweeting with Cron Jobs +- Users define a schedule file containing prompts and specific times for tweet generation. +- A cron job processes the schedule, generates tweets based on the provided prompt, and automatically publishes them. + +### ๐Ÿ”— Automated Tweeting via ChainGPT Webhooks +- Users register their webhook with the ChainGPT portal and subscribe to specific news categories. +- When ChainGPT publishes news in a subscribed category, the project receives a relevant tweet and posts it automatically. + +--- +## ๐Ÿ›  Requirements +- A **ChainGPT API key** is required for authentication and access to the service. +- **Each tweet generation consumes 1 credit.** + +--- +## ๐Ÿ”‘ How to Generate a ChainGPT API Key +To use this project, follow these steps: + +### 1๏ธโƒฃ Access the API Dashboard +- Go to the **[ChainGPT API Dashboard](https://app.chaingpt.org/apidashboard)**. +- Connect your crypto wallet to authenticate. + +### 2๏ธโƒฃ Create an API Key +- Click **"Create New Key"**. +- Copy and securely store the secret phrase (required for authentication). + +### 3๏ธโƒฃ Purchase Credits +- Visit **[ChainGPT Credits](https://app.chaingpt.org/addcredits)**. +- Buy credits as **each tweet generation consumes 1 credit**. + +Once you have your API key and credits, you're ready to start! ๐Ÿš€ + +--- +## ๐Ÿ”„ Working Steps +Before running the project, follow these setup steps. + +### ๐Ÿ”‘ Load Twitter Access Tokens +Before generating tweets, you need to load your Twitter Access Token and Refresh Token into the project. + +**Request:** +```http POST {projectUrl}/api/tokens/ -Body: +``` +**Body:** +```json { -"accessToken": "", -"refreshToken": "" + "accessToken": "", + "refreshToken": "" } +``` +Once done, the project can authenticate and post tweets on your behalf. -Once this is done, the project will be able to authenticate and post tweets on your behalf. - -Flow 1: Tweet Using Cron Job -In this workflow, tweets are generated based on predefined prompts and times stored in a JSON file. -Step 2: Define Your Schedule -Create a schedule.json file with the following format: +--- +## ๐Ÿ” Tweeting Workflows +### ๐Ÿ“… Flow 1: Scheduled Tweeting via Cron Job +#### 1๏ธโƒฃ Define Your Schedule +Create a `schedule.json` file in the following format: +```json { -"14:30": "The future of AI", -"18:00": "Crypto markets are evolving" + "14:30": "The future of AI", + "18:00": "Crypto markets are evolving" } +``` +- **Time:** Must be in **UTC format** (`HH:MM`). +- **Prompt:** The text used to generate the tweet. -time: Must be in UTC format (HH:MM). -prompt: The text that will be used to generate the tweet. -Step 3: Run the Cron Job -The cron job will execute automatically at the specified times, generating tweets using chainGPT tweet generation feature and publishing them to Twitter. +#### 2๏ธโƒฃ Run the Cron Job +The cron job executes automatically at the scheduled times, generating tweets using ChainGPT's tweet generation feature and publishing them. -Flow 2: Subscribe to ChainGPT Categories -In this workflow, users can subscribe to specific categories available on ChainGPT. When a news article is published in a subscribed category, the project will automatically generate a tweet and post it to Twitter. -Step 1: Get Available Categories -To see all available categories and your currently subscribed ones, use the following API: -Request: -GET https://appapi.chaingpt.dev/category-subscription/ +--- +### ๐Ÿ”” Flow 2: Tweeting via ChainGPT News Categories +#### 1๏ธโƒฃ Get Available Categories +Retrieve all available categories and your subscribed ones. -Headers: +**Request:** +```http +GET https://webapi.chaingpt.org/category-subscription/ +``` +**Headers:** +```json { -"api-key": "" + "api-key": "" } -This will return a list of all categories along with the categories you are currently subscribed to. +``` +This returns a list of categories along with your current subscriptions. -Step 2: Subscribe to Categories -To subscribe to one or more categories, send a POST request with the category IDs you want to subscribe to. -Request: -POST https://appapi.chaingpt.dev/category-subscription/subscribe +#### 2๏ธโƒฃ Subscribe to Categories +To subscribe, send a `POST` request with the category IDs. -Headers: +**Request:** +```http +POST https://webapi.chaingpt.org/category-subscription/subscribe +``` +**Headers:** +```json { -"api-key": "" + "api-key": "" } -Body: +``` +**Body:** +```json { -"categoryIds": [2, 3] + "categoryIds": [2, 3] } -Here, 2 and 3 correspond to category id you want to tweet about -Step 3: Register Webhook with ChainGPT -After subscribing to categories, you must register your webhook with ChainGPT. This ensures that whenever a news article is published in a subscribed category, the webhook receives the update and automatically generates a tweet. -Register Webhook -Send a POST request to your project's webhook registration route: -Request: +``` +*(IDs 2 and 3 correspond to the categories you want to tweet about.)* + +#### 3๏ธโƒฃ Register Webhook with ChainGPT +Register your webhook with ChainGPT to receive updates. + +**Request:** +```http POST {base_url_of_your_project}/api/webhook/register -Body: +``` +**Body:** +```json { -"url": "{base_url_of_your_project}/api/webhook/" + "url": "{base_url_of_your_project}/api/webhook/" } -How It Works: -This registers your webhook URL with ChainGPT. -When ChainGPT publishes news in your subscribed categories, it will send a POST request to your webhook. -The project will process the incoming tweet and post it to Twitter. -With this setup, the entire process is automated, ensuring real-time tweet updates for the latest news in your subscribed categories. - -Project Setup Guide ๐Ÿš€ -Prerequisites -Ensure you have the following installed on your system: -Node.js (Latest LTS recommended) -npm (Installed with Node.js) -How to Run the Project -Follow these steps to set up and start the project: - -1. Install Dependencies - npm install - -2. Start the Project - Use the following command to start the project: - npm run start - -3. Configure Environment Variables - Create a .env file in the project's root directory and add the following fields: - CLIENT_ID= - CLIENT_SECRET= - ENCRYPTION_KEY= - IV= - ENCRYPTION_SALT= - WEB_URL=https://appapi.chaingpt.dev - -4. Run the Project - Once the .env file is set up, restart the project to ensure the environment variables are loaded. - Now your project should be up and running! ๐Ÿš€ +``` +#### ๐Ÿ”น How It Works: +- This registers your webhook URL with ChainGPT. +- When ChainGPT publishes news in your subscribed categories, it sends a `POST` request to your webhook. +- The project processes the tweet and posts it to Twitter. + +๐Ÿ”น *With this setup, the entire process is automated, ensuring real-time tweet updates for the latest news in your subscribed categories!* + +--- +## ๐Ÿ“Œ Project Setup Guide +### โœ… Prerequisites +Ensure the following are installed on your system: +- **Node.js** (Latest LTS recommended) +- **npm** (Installed with Node.js) + +### ๐Ÿ“ฅ How to Run the Project +#### 1๏ธโƒฃ Install Dependencies +```bash +npm install +``` +#### 2๏ธโƒฃ Start the Project +```bash +npm run start +``` +#### 3๏ธโƒฃ Configure Environment Variables +Create a `.env` file in the project's root directory and add the following fields: +```ini +CLIENT_ID= +CLIENT_SECRET= +ENCRYPTION_KEY= +IV= +ENCRYPTION_SALT= +WEB_URL=https://webapi.chaingpt.org +``` +#### 4๏ธโƒฃ Run the Project +Once the `.env` file is set up, restart the project to load environment variables. + +๐ŸŽ‰ **Now your project is up and running!** ๐Ÿš€ + +--- +## ๐Ÿค Contributing +We welcome contributions! If you'd like to contribute, please follow these steps: +1. **Fork the repository**. +2. **Create a new branch** (`git checkout -b feature-branch`). +3. **Make your changes and commit** (`git commit -m 'Add new feature'`). +4. **Push to the branch** (`git push origin feature-branch`). +5. **Open a Pull Request**. + +--- +## ๐Ÿ“ง Support +If you encounter any issues or need assistance, feel free to reach out via GitHub issues. + +๐Ÿ‘จโ€๐Ÿ’ป **Happy Coding!** ๐Ÿš€ + From c96fe55af22ac281c762f1412e3885e8e685b542 Mon Sep 17 00:00:00 2001 From: maryamns <128455970+maryamns@users.noreply.github.com> Date: Tue, 4 Mar 2025 07:46:07 +0500 Subject: [PATCH 008/100] Update README.md --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 70a6a6c..d4fb93b 100644 --- a/README.md +++ b/README.md @@ -160,16 +160,6 @@ Once the `.env` file is set up, restart the project to load environment variable ๐ŸŽ‰ **Now your project is up and running!** ๐Ÿš€ ---- -## ๐Ÿค Contributing -We welcome contributions! If you'd like to contribute, please follow these steps: -1. **Fork the repository**. -2. **Create a new branch** (`git checkout -b feature-branch`). -3. **Make your changes and commit** (`git commit -m 'Add new feature'`). -4. **Push to the branch** (`git push origin feature-branch`). -5. **Open a Pull Request**. - ---- ## ๐Ÿ“ง Support If you encounter any issues or need assistance, feel free to reach out via GitHub issues. From de71017bf272ccf23c177a21102bd7b1beffbf66 Mon Sep 17 00:00:00 2001 From: M Martinez <2624124+mmartinez@users.noreply.github.com> Date: Tue, 4 Mar 2025 09:18:13 +0100 Subject: [PATCH 009/100] first refactor --- .env.example | 15 + .gitignore | 30 + .npmrc | 2 + README.md | 271 +++--- apiRouter/apiRouter.js | 9 - apiRouter/token.js | 8 - apiRouter/webhook.js | 12 - controllers/tokenController.js | 21 - controllers/webhookController.js | 49 - data/schedule.json | 73 ++ jobs/postTweetJob.js | 38 - package-lock.json | 1182 ------------------------- package.json | 26 +- schedule.json | 20 - server.js | 33 - services/tokens.json | 4 - services/twitterService.js | 222 ----- src/config/env.ts | 46 + src/controllers/token.controller.ts | 38 + src/controllers/webhook.controller.ts | 86 ++ src/index.ts | 57 ++ src/jobs/tweet.job.ts | 79 ++ src/routes/index.ts | 12 + src/routes/token.routes.ts | 10 + src/routes/webhook.routes.ts | 11 + src/services/twitter.service.ts | 181 ++++ src/types/index.ts | 88 ++ src/utils/encryption.ts | 143 +++ tsconfig.json | 10 + 29 files changed, 1040 insertions(+), 1736 deletions(-) create mode 100644 .env.example create mode 100644 .npmrc delete mode 100644 apiRouter/apiRouter.js delete mode 100644 apiRouter/token.js delete mode 100644 apiRouter/webhook.js delete mode 100644 controllers/tokenController.js delete mode 100644 controllers/webhookController.js create mode 100644 data/schedule.json delete mode 100644 jobs/postTweetJob.js delete mode 100644 package-lock.json delete mode 100644 schedule.json delete mode 100644 server.js delete mode 100644 services/tokens.json delete mode 100644 services/twitterService.js create mode 100644 src/config/env.ts create mode 100644 src/controllers/token.controller.ts create mode 100644 src/controllers/webhook.controller.ts create mode 100644 src/index.ts create mode 100644 src/jobs/tweet.job.ts create mode 100644 src/routes/index.ts create mode 100644 src/routes/token.routes.ts create mode 100644 src/routes/webhook.routes.ts create mode 100644 src/services/twitter.service.ts create mode 100644 src/types/index.ts create mode 100644 src/utils/encryption.ts create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a3ba9c7 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Server Configuration +PORT=8000 +NODE_ENV=development + +# Twitter API Credentials +CLIENT_ID=your_twitter_client_id +CLIENT_SECRET=your_twitter_client_secret + +# Encryption Settings +ENCRYPTION_KEY=your_32_character_encryption_key +ENCRYPTION_SALT=your_hex_encryption_salt +IV=your_hex_initialization_vector + +# ChainGPT API +CHAINGPT_API_KEY=your_chaingpt_api_key \ No newline at end of file diff --git a/.gitignore b/.gitignore index b4949cf..7b0e802 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,33 @@ node_modules/ #env .env + +# Bun files +node_modules/ +.env +bun.lockb + +# Build output +dist/ + +# Data files with sensitive information +data/tokens.json + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# OS files +.DS_Store +Thumbs.db diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f68dfcc --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +use-bun=true \ No newline at end of file diff --git a/README.md b/README.md index d4fb93b..b5288c4 100644 --- a/README.md +++ b/README.md @@ -1,167 +1,176 @@ -# ๐Ÿ“ข Automated Tweeting with ChainGPT +# Twitter AI Agent -## ๐Ÿš€ Project Overview -This open-source project provides two key features for automated tweet generation and posting: +A TypeScript-based Twitter bot that automatically generates and posts tweets on a schedule using AI. Built with Hono, TypeScript, and Bun runtime. -### ๐Ÿ•’ Scheduled Tweeting with Cron Jobs -- Users define a schedule file containing prompts and specific times for tweet generation. -- A cron job processes the schedule, generates tweets based on the provided prompt, and automatically publishes them. +## Features -### ๐Ÿ”— Automated Tweeting via ChainGPT Webhooks -- Users register their webhook with the ChainGPT portal and subscribe to specific news categories. -- When ChainGPT publishes news in a subscribed category, the project receives a relevant tweet and posts it automatically. +- ๐Ÿค– AI-powered tweet generation using ChainGPT +- ๐Ÿ“… Scheduled tweets based on configurable prompts +- ๐Ÿ”’ Secure token storage with encryption +- ๐ŸŒ Webhook support for external integrations +- ๐Ÿ”„ Automatic token refresh for Twitter API +- ๐Ÿš€ Ultra-fast Bun runtime for improved performance +- ๐Ÿ“Š TypeScript for type safety and better developer experience ---- -## ๐Ÿ›  Requirements -- A **ChainGPT API key** is required for authentication and access to the service. -- **Each tweet generation consumes 1 credit.** +## Prerequisites ---- -## ๐Ÿ”‘ How to Generate a ChainGPT API Key -To use this project, follow these steps: +- [Bun](https://bun.sh) 1.0+ runtime (faster alternative to Node.js) +- Twitter API credentials (OAuth 2.0) +- ChainGPT API key -### 1๏ธโƒฃ Access the API Dashboard -- Go to the **[ChainGPT API Dashboard](https://app.chaingpt.org/apidashboard)**. -- Connect your crypto wallet to authenticate. +## Installation -### 2๏ธโƒฃ Create an API Key -- Click **"Create New Key"**. -- Copy and securely store the secret phrase (required for authentication). +1. Clone the repository: + ```bash + git clone https://github.com/yourusername/twitter-ai-agent.git + cd twitter-ai-agent + ``` -### 3๏ธโƒฃ Purchase Credits -- Visit **[ChainGPT Credits](https://app.chaingpt.org/addcredits)**. -- Buy credits as **each tweet generation consumes 1 credit**. +2. Install Bun if you don't have it already: + ```bash + curl -fsSL https://bun.sh/install | bash + ``` -Once you have your API key and credits, you're ready to start! ๐Ÿš€ +3. Install dependencies with Bun: + ```bash + bun install + ``` ---- -## ๐Ÿ”„ Working Steps -Before running the project, follow these setup steps. +4. Create a `.env` file based on the example: + ```bash + cp .env.example .env + ``` -### ๐Ÿ”‘ Load Twitter Access Tokens -Before generating tweets, you need to load your Twitter Access Token and Refresh Token into the project. +5. Fill in your environment variables in the `.env` file: + ``` + # Server Configuration + PORT=8000 + NODE_ENV=development -**Request:** -```http -POST {projectUrl}/api/tokens/ -``` -**Body:** -```json -{ - "accessToken": "", - "refreshToken": "" -} -``` -Once done, the project can authenticate and post tweets on your behalf. + # Twitter API Credentials + CLIENT_ID=your_twitter_client_id + CLIENT_SECRET=your_twitter_client_secret ---- -## ๐Ÿ” Tweeting Workflows -### ๐Ÿ“… Flow 1: Scheduled Tweeting via Cron Job -#### 1๏ธโƒฃ Define Your Schedule -Create a `schedule.json` file in the following format: -```json -{ - "14:30": "The future of AI", - "18:00": "Crypto markets are evolving" -} -``` -- **Time:** Must be in **UTC format** (`HH:MM`). -- **Prompt:** The text used to generate the tweet. + # Encryption Settings + ENCRYPTION_KEY=your_32_character_encryption_key + ENCRYPTION_SALT=your_hex_encryption_salt + IV=your_hex_initialization_vector -#### 2๏ธโƒฃ Run the Cron Job -The cron job executes automatically at the scheduled times, generating tweets using ChainGPT's tweet generation feature and publishing them. + # ChainGPT API + CHAINGPT_API_KEY=your_chaingpt_api_key + ``` ---- -### ๐Ÿ”” Flow 2: Tweeting via ChainGPT News Categories -#### 1๏ธโƒฃ Get Available Categories -Retrieve all available categories and your subscribed ones. +## Usage -**Request:** -```http -GET https://webapi.chaingpt.org/category-subscription/ -``` -**Headers:** -```json -{ - "api-key": "" -} +### Development Mode + +```bash +bun dev ``` -This returns a list of categories along with your current subscriptions. -#### 2๏ธโƒฃ Subscribe to Categories -To subscribe, send a `POST` request with the category IDs. +### Production Mode -**Request:** -```http -POST https://webapi.chaingpt.org/category-subscription/subscribe +```bash +bun build +bun start ``` -**Headers:** + +## API Endpoints + +### Token Management + +- **POST /api/tokens** + - Load Twitter API tokens + - Body: `{ "accessToken": "your_access_token", "refreshToken": "your_refresh_token" }` + +### Webhook Management + +- **POST /api/webhook/register** + - Register a webhook with ChainGPT + - Body: `{ "url": "https://your-webhook-url.com" }` + +- **POST /api/webhook** + - Receive a tweet from a webhook and post it + - Body: `{ "tweet": "Your tweet content" }` + +## Tweet Scheduling + +The application uses a schedule defined in `data/schedule.json` to post tweets at specific times. The format is: + ```json { - "api-key": "" + "HH:MM": "Prompt for generating tweet content" } ``` -**Body:** + +For example: ```json { - "categoryIds": [2, 3] + "09:00": "Create a tweet about the latest crypto news", + "15:30": "Generate a meme about blockchain technology" } ``` -*(IDs 2 and 3 correspond to the categories you want to tweet about.)* -#### 3๏ธโƒฃ Register Webhook with ChainGPT -Register your webhook with ChainGPT to receive updates. +## Project Structure -**Request:** -```http -POST {base_url_of_your_project}/api/webhook/register ``` -**Body:** -```json -{ - "url": "{base_url_of_your_project}/api/webhook/" -} +twitter-ai-agent/ +โ”œโ”€โ”€ data/ # Data storage +โ”‚ โ””โ”€โ”€ schedule.json # Tweet schedule configuration +โ”œโ”€โ”€ src/ # Source code +โ”‚ โ”œโ”€โ”€ config/ # Configuration +โ”‚ โ”‚ โ””โ”€โ”€ env.ts # Environment variables validation +โ”‚ โ”œโ”€โ”€ controllers/ # API controllers +โ”‚ โ”‚ โ”œโ”€โ”€ token.controller.ts +โ”‚ โ”‚ โ””โ”€โ”€ webhook.controller.ts +โ”‚ โ”œโ”€โ”€ jobs/ # Scheduled jobs +โ”‚ โ”‚ โ””โ”€โ”€ tweet.job.ts # Tweet scheduling +โ”‚ โ”œโ”€โ”€ routes/ # API routes +โ”‚ โ”‚ โ”œโ”€โ”€ index.ts # Main router +โ”‚ โ”‚ โ”œโ”€โ”€ token.routes.ts +โ”‚ โ”‚ โ””โ”€โ”€ webhook.routes.ts +โ”‚ โ”œโ”€โ”€ services/ # Business logic +โ”‚ โ”‚ โ””โ”€โ”€ twitter.service.ts +โ”‚ โ”œโ”€โ”€ types/ # TypeScript type definitions +โ”‚ โ”‚ โ””โ”€โ”€ index.ts +โ”‚ โ”œโ”€โ”€ utils/ # Utility functions +โ”‚ โ”‚ โ””โ”€โ”€ encryption.ts # Token encryption +โ”‚ โ””โ”€โ”€ index.ts # Application entry point +โ”œโ”€โ”€ .env # Environment variables +โ”œโ”€โ”€ .env.example # Example environment variables +โ”œโ”€โ”€ package.json # Dependencies and scripts +โ””โ”€โ”€ tsconfig.json # TypeScript configuration ``` -#### ๐Ÿ”น How It Works: -- This registers your webhook URL with ChainGPT. -- When ChainGPT publishes news in your subscribed categories, it sends a `POST` request to your webhook. -- The project processes the tweet and posts it to Twitter. - -๐Ÿ”น *With this setup, the entire process is automated, ensuring real-time tweet updates for the latest news in your subscribed categories!* - ---- -## ๐Ÿ“Œ Project Setup Guide -### โœ… Prerequisites -Ensure the following are installed on your system: -- **Node.js** (Latest LTS recommended) -- **npm** (Installed with Node.js) - -### ๐Ÿ“ฅ How to Run the Project -#### 1๏ธโƒฃ Install Dependencies -```bash -npm install -``` -#### 2๏ธโƒฃ Start the Project -```bash -npm run start -``` -#### 3๏ธโƒฃ Configure Environment Variables -Create a `.env` file in the project's root directory and add the following fields: -```ini -CLIENT_ID= -CLIENT_SECRET= -ENCRYPTION_KEY= -IV= -ENCRYPTION_SALT= -WEB_URL=https://webapi.chaingpt.org -``` -#### 4๏ธโƒฃ Run the Project -Once the `.env` file is set up, restart the project to load environment variables. -๐ŸŽ‰ **Now your project is up and running!** ๐Ÿš€ +## Why Bun? + +This project uses Bun instead of Node.js for several key benefits: + +- **Improved Performance**: Significantly faster startup and execution times +- **Built-in TypeScript Support**: No need for separate compilation steps +- **Modern JavaScript Features**: First-class support for ESM and top-level await +- **Simplified Development**: Built-in tools for testing, bundling, and running +- **Compatible with Node.js**: Works with most npm packages + +## Security + +- All Twitter tokens are encrypted using Web Crypto API +- Environment variables are validated at startup +- Proper error handling throughout the application + +## Contributing + +1. Fork the repository +2. Create your feature branch: `git checkout -b feature/my-new-feature` +3. Commit your changes: `git commit -am 'Add some feature'` +4. Push to the branch: `git push origin feature/my-new-feature` +5. Submit a pull request + +## License + +ISC -## ๐Ÿ“ง Support -If you encounter any issues or need assistance, feel free to reach out via GitHub issues. +## Author -๐Ÿ‘จโ€๐Ÿ’ป **Happy Coding!** ๐Ÿš€ +soroojshehryar01@gmail.com diff --git a/apiRouter/apiRouter.js b/apiRouter/apiRouter.js deleted file mode 100644 index 70de506..0000000 --- a/apiRouter/apiRouter.js +++ /dev/null @@ -1,9 +0,0 @@ -const express = require("express"); -const app = express(); -const webhookRoutes = require("./webhook"); -const tokensRoutes = require("./token"); - -app.use("/webhook", webhookRoutes); -app.use("/tokens", tokensRoutes); - -module.exports = app; diff --git a/apiRouter/token.js b/apiRouter/token.js deleted file mode 100644 index a2c9348..0000000 --- a/apiRouter/token.js +++ /dev/null @@ -1,8 +0,0 @@ -const express = require("express"); -const { loadTokens } = require("../controllers/tokenController"); - -const router = express.Router(); - -router.post("/", loadTokens); - -module.exports = router; diff --git a/apiRouter/webhook.js b/apiRouter/webhook.js deleted file mode 100644 index 838d179..0000000 --- a/apiRouter/webhook.js +++ /dev/null @@ -1,12 +0,0 @@ -const express = require("express"); -const { - registerWebhook, - tweetWebhook, -} = require("../controllers/webhookController"); - -const router = express.Router(); - -router.post("/register", registerWebhook); -router.post("/", tweetWebhook); - -module.exports = router; diff --git a/controllers/tokenController.js b/controllers/tokenController.js deleted file mode 100644 index f5a212a..0000000 --- a/controllers/tokenController.js +++ /dev/null @@ -1,21 +0,0 @@ -const { saveTokens } = require("../services/twitterService"); - -// api to encrypt twitter access and refresh token then load tokens into token file -const loadTokens = async (req, res, next) => { - try { - const encryptionKey = process.env.ENCRYPTION_KEY; - const { accessToken, refreshToken } = req.body; - if (!accessToken || !refreshToken) { - throw new Error("accessToken & refreshToken are required"); - } - saveTokens(accessToken, refreshToken, encryptionKey); - res.send({ - message: "tokens saved", - }); - } catch (err) { - console.log(`error registering webhook ${err}`); - next(err); - } -}; - -module.exports = { loadTokens }; diff --git a/controllers/webhookController.js b/controllers/webhookController.js deleted file mode 100644 index 86aadf8..0000000 --- a/controllers/webhookController.js +++ /dev/null @@ -1,49 +0,0 @@ -const axios = require("axios"); -const { uploadTwitterPostTweet } = require("../services/twitterService"); - -//api to register webhook with chaingpt -const registerWebhook = async (req, res, next) => { - try { - const { url } = req.body; - if (!url) { - throw new Error("url is required"); - } - const response = await axios.post( - `${process.env.WEB_URL}/webhook-subscription/register`, - { - url, - }, - { - headers: { - "api-key": process.env.API_KEY, - }, - } - ); - // return response.data; - res.send({ - message: "webhook registered successfully", - response: response.data, - }); - } catch (err) { - console.log(`error registering webhook ${err}`); - next(err); - } -}; - -// webhook to receive tweet and publish it -const tweetWebhook = async (req, res, next) => { - try { - const { tweet } = req.body; - if (!tweet) { - throw new Error("tweet is required"); - } - - await uploadTwitterPostTweet(tweet); - res.send({ message: "tweeted successfully", tweet }); - } catch (err) { - console.log(`error posting tweet using webhook ${err}`); - next(err); - } -}; - -module.exports = { registerWebhook, tweetWebhook }; diff --git a/data/schedule.json b/data/schedule.json new file mode 100644 index 0000000..0428803 --- /dev/null +++ b/data/schedule.json @@ -0,0 +1,73 @@ +{ + "config": { + "persona": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart", + "maxLength": 280, + "timezone": "UTC" + }, + "schedule": { + "00:00": { + "type": "poll", + "instruction": "{{persona}} and good at analyzing the crypto market. Create fun, engaging polls for your audience to spark conversation. Provide 1 poll. It will be a poll on Twitter, so please provide it in the correct template. Only provide this information: Title / Question To Ask Option #1 Option #2 Option #3 Option #4" + }, + "00:30": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." + }, + "03:00": { + "type": "education", + "instruction": "{{persona}} and good at explaining complex crypto concepts and AI concepts in simple terms. Create a short, engaging tweet explaining one key blockchain or crypto concept in less than {{maxLength}} characters." + }, + "04:30": { + "type": "market_recap", + "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." + }, + "06:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:30": { + "type": "market_insight", + "instruction": "{{persona}} and good at market analysis. Write something about the market based on the fear & greed index and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters." + }, + "09:00": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." + }, + "10:30": { + "type": "nft", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the most hyped NFT collections and their volumes." + }, + "12:00": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "13:30": { + "type": "market_analysis", + "instruction": "{{persona}} and excellent at market analysis. Create a crypto market analysis report showing all the crypto related indicators that could be helpful. Start the report with: Here's a Comprehensive Analysis Report on Key Crypto Market Indicators: (Expand the tweet to view it in full ๐Ÿ‘‡)" + }, + "15:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "16:30": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." + }, + "18:00": { + "type": "trending_tokens", + "instruction": "{{persona}} and good at market research. Name 10 tokens that are trending right now. Write about it in this form: Hey fam, here are the top ten tokens trending right now ๐Ÿ‘‡ DYOR: $NAME $NAME $NAME $NAME. Top trending tokens on coinmarketcap" + }, + "19:30": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet about the latest and most important crypto news." + }, + "21:00": { + "type": "market_shift", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) announcing a major crypto market change. Based on all the information, market indicators, and news you have. Post about the most important or interesting market shift or event that is happening right now." + }, + "22:30": { + "type": "market_insight", + "instruction": "{{persona}} and good at market analysis. Write something about the market based on the bitcoin vs alts vs stablecoin dominance and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters, tweet style." + } + } +} \ No newline at end of file diff --git a/jobs/postTweetJob.js b/jobs/postTweetJob.js deleted file mode 100644 index a4644ec..0000000 --- a/jobs/postTweetJob.js +++ /dev/null @@ -1,38 +0,0 @@ -const cron = require("node-cron"); -const path = require("path"); -const fs = require("fs"); -const { generateAndPostTweet } = require("../services/twitterService"); - -const configPath = path.join(__dirname, "../schedule.json"); - -function loadConfig() { - try { - const data = fs.readFileSync(configPath, "utf8"); - return JSON.parse(data); - } catch (error) { - console.error("Error reading schedule config:", error); - return {}; - } -} - -// cron job to read schedule.json file and schedule tweet generation and posting according to prompt -function scheduleTweets() { - const config = loadConfig(); - for (const time in config) { - const prompt = config[time]; - const [hour, minute] = time.split(":"); - - cron.schedule( - `${minute} ${hour} * * *`, - () => { - console.log(`Running scheduled tweet for UTC time: ${time}`); - generateAndPostTweet(prompt); - }, - { - timezone: "UTC", - } - ); - } -} - -module.exports = { scheduleTweets }; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b680a8d..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1182 +0,0 @@ -{ - "name": "twitter-ai-agent", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "twitter-ai-agent", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "axios": "^1.7.9", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "node-cron": "^3.0.3", - "nodemon": "^3.1.9" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "function-bind": "^1.1.2", - "get-proto": "^1.0.0", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", - "dependencies": { - "uuid": "8.3.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - } - } -} diff --git a/package.json b/package.json index ac8fc26..f620a25 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,32 @@ { "name": "twitter-ai-agent", "version": "1.0.0", - "main": "server.js", + "main": "src/index.ts", + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "dev": "nodemon server.js", - "start": "node server.js" + "dev": "bun --watch src/index.ts", + "build": "bun build ./src/index.ts --outdir ./dist --target node", + "start": "bun src/index.ts", + "lint": "eslint 'src/**/*.ts'", + "format": "prettier --write 'src/**/*.ts'", + "test": "bun test" }, "author": "soroojshehryar01@gmail.com", "license": "ISC", "description": "", "dependencies": { "axios": "^1.7.9", - "dotenv": "^16.4.7", - "express": "^4.21.2", + "hono": "^4.1.5", "node-cron": "^3.0.3", - "nodemon": "^3.1.9" + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "bun-types": "^1.2.4", + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "typescript": "^5.4.2" } } diff --git a/schedule.json b/schedule.json deleted file mode 100644 index 1ea37bb..0000000 --- a/schedule.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "00:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create fun, engaging polls for your audience to spark conversation. Provide 1 poll. it will be a poll on twitter, so please provide it in the correct template. Only provide this information: Title / Question To Ask Option #1 Option #2 Option #3 Option #4", - "0:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the latest and most important crypto news.", - "03:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at explaining complex crypto concepts and AI concepts in simple terms. Create a short, engaging tweet explaining one key blockchain or crypto concept in less than 280 characters.", - - "04:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than 280 characters. \n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread.", - - "06:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and excellent at spotting key market movements. Create a tweet (less than 280 characters) that's a meme about crypto.", - "07:30": "Write something about the market based on the fear & greed index and other indicators. What is the conclusion. Make it short, less than 280 characters.", - "09:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the latest and most important crypto news.", - "10:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the most hyped NFT collections and their volumes.", - "12:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at educating people about crypto security. Create a tweet (less than 280 characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, โ€˜Daily security tip, from your favorite AI Agentโ€™ and include a security-related emoji. Ensure the tip is distinct and doesnโ€™t repeat the style, structure, or examples from previous responses.", - "13:30": "Create a crypto market analysis report showing all the crypto related indicators that could be helpful. Start the report with : Here's a Comprehensive Analysis Report on Key Crypto Market Indicators: (Expand the tweet to view it in full ๐Ÿ‘‡)", - "15:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and excellent at spotting key market movements. Create a tweet (less than 280 characters) that's a meme about crypto.", - "16:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet (less than 280 characters) about the latest and most important crypto news.", - "18:00": "Name 10 tokens that are trending right now. Write about it in this form: Hey fam, here are the top ten tokens trending right now ๐Ÿ‘‡ DYOR: $NAME $NAME $NAME $NAME. Top trending tokens on coinmarketcap", - "19:30": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and good at analyzing the crypto market. Create a tweet about the latest and most important crypto news.", - "21:00": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart, and excellent at spotting key market movements. Create a tweet (less than 280 characters) announcing a major crypto market change. Based on all the information , and market indicators, and news you have. Post about the most important or interesting market shift or event that is happening right now.", - "22:30": "Write something about the market based on the bitcoin vs alts vs stablecoin dominance and other indicators. What is the conclusion. Make it short, less than 280 characters, tweet style." -} diff --git a/server.js b/server.js deleted file mode 100644 index 272d069..0000000 --- a/server.js +++ /dev/null @@ -1,33 +0,0 @@ -require("dotenv").config(); -const express = require("express"); -const { scheduleTweets } = require("./jobs/postTweetJob"); -const apiRoutes = require("./apiRouter/apiRouter"); - -const app = express(); -const port = process.env.PORT || 8000; - -app.use(express.json()); - -// Default Route -app.get("/", (req, res) => { - res.send("Hi, this is Twitter AI Agent developed by ChainGPT"); -}); - -app.use("/api", apiRoutes); - -scheduleTweets(); -console.log("Tweet scheduler started."); - -// Global Error Handling Middleware -app.use((err, req, res, next) => { - console.error("Global Error Handler:", err.message); - - res.status(err.status || 500).json({ - message: err.message || "Internal Server Error", - status: err.status || 500, - }); -}); - -app.listen(port, () => { - console.log(`๐Ÿš€ Twitter AI Agent listening on port ${port}`); -}); diff --git a/services/tokens.json b/services/tokens.json deleted file mode 100644 index 4b82263..0000000 --- a/services/tokens.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "accessToken": "", - "refreshToken": "" -} diff --git a/services/twitterService.js b/services/twitterService.js deleted file mode 100644 index 5787bab..0000000 --- a/services/twitterService.js +++ /dev/null @@ -1,222 +0,0 @@ -const axios = require("axios"); -const crypto = require("crypto"); -const fs = require("fs"); -const path = require("path"); - -require("dotenv").config(); - -const clientId = process.env.CLIENT_ID; -const clientSecret = process.env.CLIENT_SECRET; -const encryptionKey = process.env.ENCRYPTION_KEY; - -// Encryption settings -const algorithm = "aes-256-gcm"; // Authenticated encryption -const keyLength = 32; // 256-bit key - -// Use a constant salt and IV from environment variables -const salt = Buffer.from(process.env.ENCRYPTION_SALT, "hex"); -const iv = Buffer.from(process.env.IV, "hex"); - -// Derive a secure key from the encryption key in the environment -function deriveKey(encryptionKey) { - return crypto.scryptSync(encryptionKey, salt, keyLength); -} - -// Encrypt data -function encrypt(text, encryptionKey) { - const key = deriveKey(encryptionKey); - const cipher = crypto.createCipheriv(algorithm, key, iv); - - let encrypted = cipher.update(text, "utf8", "hex"); - encrypted += cipher.final("hex"); - - const authTag = cipher.getAuthTag().toString("hex"); - - return `${authTag}:${encrypted}`; // IV is constant, no need to include it each time -} - -// Decrypt data -function decrypt(encryptedText, encryptionKey) { - const key = deriveKey(encryptionKey); - const [authTagHex, encrypted] = encryptedText.split(":"); - const authTag = Buffer.from(authTagHex, "hex"); - - const decipher = crypto.createDecipheriv(algorithm, key, iv); - decipher.setAuthTag(authTag); - - let decrypted = decipher.update(encrypted, "hex", "utf8"); - decrypted += decipher.final("utf8"); - - return decrypted; -} - -// Save tokens to a file -function saveTokens(accessToken, refreshToken, encryptionKey) { - const tokens = { - accessToken: encrypt(accessToken, encryptionKey), - refreshToken: encrypt(refreshToken, encryptionKey), - }; - fs.writeFileSync(path.join(__dirname, "tokens.json"), JSON.stringify(tokens)); -} - -// Load tokens from a file -function loadTokens(encryptionKey) { - try { - const tokens = JSON.parse( - fs.readFileSync(path.join(__dirname, "tokens.json")) - ); - const accessToken = decrypt(tokens.accessToken, encryptionKey); - const refreshToken = decrypt(tokens.refreshToken, encryptionKey); - return { accessToken, refreshToken }; - } catch (error) { - console.error("Error loading tokens:", error.message); - return null; - } -} - -//get access and refresh token after decrypting, it access token is expired generate new access token using refresh token -const getAcesstokenOftwitterExtra = async () => { - let { accessToken, refreshToken } = loadTokens(encryptionKey); - - if (!accessToken) { - throw new Error("No active Twitter authorization found."); - } - - try { - const response = await axios.get("https://api.twitter.com/2/users/me", { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - - return accessToken; - } catch (error) { - console.error( - "๐Ÿš€ Twitter Token Check Error:", - error.response?.status || error.message - ); - - if (error.response?.status === 401) { - try { - const { newAccessToken, newRefreshToken } = await refreshAccessToken( - refreshToken - ); - saveTokens(newAccessToken, newRefreshToken, encryptionKey); - - return newAccessToken; - } catch (refreshError) { - console.error("Failed to refresh token:", refreshError.message); - throw refreshError; - } - } - - throw error; - } -}; - -// generate new access and refresh token from refresh token -const refreshAccessToken = async (refreshToken) => { - try { - console.log("refreshing access token"); - const base64Credentials = Buffer.from( - `${clientId}:${clientSecret}` - ).toString("base64"); - - const response = await axios.post( - "https://api.twitter.com/2/oauth2/token", - new URLSearchParams({ - grant_type: "refresh_token", - refresh_token: refreshToken, - }).toString(), - { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${base64Credentials}`, - }, - } - ); - - console.log("refreshed access token"); - - return { - newAccessToken: response.data.access_token, - newRefreshToken: response.data.refresh_token, - }; - } catch (error) { - console.error( - "Error refreshing access token:", - error.response?.data || error.message - ); - throw error; - } -}; - -// get text to tweet using cron scheduled job -const getTextForTweet = async (prompt) => { - try { - const response = await axios.post( - `${process.env.WEB_URL}/tweet-generator`, - { - prompt, - }, - { - headers: { - "api-key": process.env.API_KEY, - }, - } - ); - return response.data.tweet; - } catch (error) { - console.error("Error fetching tweet text:", error.message); - throw new Error(`Error generating content: ${error.message}`); - } -}; - -// post tweet to twitter -const postTweet = async (accessToken, message) => { - const url = "https://api.twitter.com/2/tweets"; - - const headers = { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }; - - try { - const response = await axios.post(url, { text: message }, { headers }); - return response.data; - } catch (error) { - console.error( - "Error posting tweet:", - error.response?.data || error.message - ); - throw new Error(`Failed to post tweet: ${error.message}`); - } -}; - -const uploadTwitterPostTweet = async (message) => { - console.log("๐Ÿš€ Posting Tweet:", message); - - let accessToken; - try { - accessToken = await getAcesstokenOftwitterExtra(); - await postTweet(accessToken, message); - } catch (error) { - console.error("Failed to upload tweet:", error.message); - throw error; - } -}; - -const generateAndPostTweet = async (prompt) => { - try { - if (!prompt) { - throw new Error("prompt is required"); - } - - let tweet = await getTextForTweet(prompt); - // set limit according to your twitter apikey limits - tweet = tweet.length > 270 ? tweet.substring(0, 270) + "..." : tweet; - await uploadTwitterPostTweet(tweet); - } catch (error) { - console.error("error in posting tweet", error); - } -}; - -module.exports = { generateAndPostTweet, uploadTwitterPostTweet, saveTokens }; diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..f41c59f --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,46 @@ +import { z } from 'zod'; + +// Define environment schema with Zod +const envSchema = z.object({ + // Server Configuration + PORT: z.string().default('8000'), + NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), + + // Twitter API Credentials + CLIENT_ID: z.string().min(1, 'Twitter Client ID is required'), + CLIENT_SECRET: z.string().min(1, 'Twitter Client Secret is required'), + + // Encryption Settings + ENCRYPTION_KEY: z.string().min(32, 'Encryption key should be at least 32 characters long'), + ENCRYPTION_SALT: z.string().min(1, 'Encryption salt is required'), + IV: z.string().min(1, 'Initialization vector is required'), + + // ChainGPT API + CHAINGPT_API_KEY: z.string().min(1, 'ChainGPT API key is required'), +}); + +// Parse and validate environment variables +// Bun automatically loads .env file, no need for dotenv +const parseEnv = () => { + try { + return envSchema.parse(process.env); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors.map(err => { + return `${err.path.join('.')}: ${err.message}`; + }); + + console.error('โŒ Invalid environment variables:'); + errorMessages.forEach(message => console.error(` - ${message}`)); + process.exit(1); + } + + throw error; + } +}; + +// Export validated environment variables +export const env = parseEnv(); + +// Type definition for environment variables +export type Env = z.infer; \ No newline at end of file diff --git a/src/controllers/token.controller.ts b/src/controllers/token.controller.ts new file mode 100644 index 0000000..51ff19f --- /dev/null +++ b/src/controllers/token.controller.ts @@ -0,0 +1,38 @@ +import { Context } from 'hono'; +import { env } from '../config/env'; +import { saveTokens } from '../utils/encryption'; +import { ApiResponse, TokenLoadRequest } from '../types'; + +/** + * Load and encrypt Twitter tokens + * @param c - Hono context + * @returns Response with token save result + */ +export const loadTokens = async (c: Context): Promise => { + try { + const { accessToken, refreshToken } = await c.req.json(); + + if (!accessToken || !refreshToken) { + return c.json({ + success: false, + message: 'Access token and refresh token are required', + error: 'Missing required fields: accessToken and/or refreshToken' + }, 400); + } + + await saveTokens(accessToken, refreshToken, env.ENCRYPTION_KEY); + + return c.json({ + success: true, + message: 'Tokens saved successfully' + }); + } catch (error) { + console.error('Error saving tokens:', error); + + return c.json({ + success: false, + message: 'Failed to save tokens', + error: error instanceof Error ? error.message : 'Unknown error' + }, 500); + } +}; \ No newline at end of file diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts new file mode 100644 index 0000000..bdaba26 --- /dev/null +++ b/src/controllers/webhook.controller.ts @@ -0,0 +1,86 @@ +import { Context } from 'hono'; +import axios from 'axios'; +import { env } from '../config/env'; +import { uploadTwitterPostTweet } from '../services/twitter.service'; +import { ApiResponse, TweetWebhookRequest, WebhookRegistrationRequest } from '../types'; + +// ChainGPT API URL - hardcoded as it shouldn't change +const CHAINGPT_API_URL = 'https://api.chaingpt.org'; + +/** + * Register a webhook with ChainGPT + * @param c - Hono context + * @returns Response with registration result + */ +export const registerWebhook = async (c: Context): Promise => { + try { + const { url } = await c.req.json(); + + if (!url) { + return c.json({ + success: false, + message: 'URL is required', + error: 'Missing required field: url' + }, 400); + } + + const response = await axios.post( + `${CHAINGPT_API_URL}/webhook-subscription/register`, + { url }, + { + headers: { + 'api-key': env.CHAINGPT_API_KEY, + }, + } + ); + + return c.json({ + success: true, + message: 'Webhook registered successfully', + data: response.data + }); + } catch (error) { + console.error('Error registering webhook:', error); + + return c.json({ + success: false, + message: 'Failed to register webhook', + error: error instanceof Error ? error.message : 'Unknown error' + }, 500); + } +}; + +/** + * Handle incoming webhook requests for posting tweets + * @param c - Hono context + * @returns Response with tweet result + */ +export const tweetWebhook = async (c: Context): Promise => { + try { + const { tweet } = await c.req.json(); + + if (!tweet) { + return c.json({ + success: false, + message: 'Tweet content is required', + error: 'Missing required field: tweet' + }, 400); + } + + const response = await uploadTwitterPostTweet(tweet); + + return c.json({ + success: true, + message: 'Tweet posted successfully', + data: { tweet, response } + }); + } catch (error) { + console.error('Error posting tweet via webhook:', error); + + return c.json({ + success: false, + message: 'Failed to post tweet', + error: error instanceof Error ? error.message : 'Unknown error' + }, 500); + } +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d75dbd2 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,57 @@ +import { Hono } from 'hono'; +import { logger } from 'hono/logger'; +import { prettyJSON } from 'hono/pretty-json'; +import { env } from './config/env'; +import apiRouter from './routes'; +import { scheduleTweets } from './jobs/tweet.job'; + +// Create Hono app +const app = new Hono(); + +// Middleware +app.use('*', logger()); +app.use('*', prettyJSON()); + +// Default route +app.get('/', (c) => { + return c.json({ + message: 'Hi, this is Twitter AI Agent developed by ChainGPT', + version: '1.0.0', + status: 'running' + }); +}); + +// API routes +app.route('/api', apiRouter); + +// Error handling middleware +app.onError((err, c) => { + console.error('Global Error Handler:', err); + + return c.json({ + success: false, + message: 'Internal Server Error', + error: err.message + }, 500); +}); + +// Start the server with Bun +const port = parseInt(env.PORT); + +console.log(`Starting server in ${env.NODE_ENV} mode...`); + +// Start the server using Bun +Bun.serve({ + fetch: app.fetch, + port +}); + +console.log(`๐Ÿš€ Twitter AI Agent listening on port ${port}`); + +// Start tweet scheduler +try { + scheduleTweets(); + console.log('Tweet scheduler started successfully'); +} catch (error) { + console.error('Failed to start tweet scheduler:', error); +} \ No newline at end of file diff --git a/src/jobs/tweet.job.ts b/src/jobs/tweet.job.ts new file mode 100644 index 0000000..8a4dcaa --- /dev/null +++ b/src/jobs/tweet.job.ts @@ -0,0 +1,79 @@ +import { schedule } from 'node-cron'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { generateAndPostTweet } from '../services/twitter.service'; + +// Path to the schedule configuration file +const CONFIG_PATH = join(import.meta.dir, '../../data/schedule.json'); + +/** + * Load schedule configuration from JSON file + * @returns The schedule configuration with config and schedule entries + */ +function loadConfig(): { config: any, schedule: Record } { + try { + const data = readFileSync(CONFIG_PATH, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading schedule config:', error); + return { config: {}, schedule: {} }; + } +} + +/** + * Process template strings in the instruction + * @param instruction - The instruction template + * @param config - The configuration with replacement values + * @returns The processed instruction + */ +function processTemplate(instruction: string, config: any): string { + return instruction.replace(/\{\{(\w+)\}\}/g, (match, key) => { + return config[key] || match; + }); +} + +/** + * Schedule tweets based on configuration + * Sets up cron jobs for each time entry in the schedule + */ +export function scheduleTweets(): void { + const { config, schedule: scheduleEntries } = loadConfig(); + + if (!scheduleEntries || Object.keys(scheduleEntries).length === 0) { + console.warn('No scheduled tweets found in configuration'); + return; + } + + console.log(`Setting up ${Object.keys(scheduleEntries).length} scheduled tweets`); + + for (const time in scheduleEntries) { + const entry = scheduleEntries[time]; + const { type, instruction } = entry; + + // Process the template to replace placeholders with actual values + const processedInstruction = processTemplate(instruction, config); + + const [hour, minute] = time.split(':'); + const timezone = config.timezone || 'UTC'; + + // Schedule the cron job + schedule( + `${minute} ${hour} * * *`, + async () => { + try { + console.log(`Running scheduled tweet for ${timezone} time: ${time} (Type: ${type})`); + await generateAndPostTweet(processedInstruction); + } catch (error) { + console.error(`Error executing scheduled tweet for time ${time}:`, error); + } + }, + { + timezone, + } + ); + + console.log(`Scheduled ${type} tweet for ${time} ${timezone}`); + } + + console.log('Tweet scheduler started successfully'); +} \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..4fa71e1 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,12 @@ +import { Hono } from 'hono'; +import webhookRouter from './webhook.routes'; +import tokenRouter from './token.routes'; + +// Create a Hono router for API routes +const apiRouter = new Hono(); + +// Register API routes +apiRouter.route('/webhook', webhookRouter); +apiRouter.route('/tokens', tokenRouter); + +export default apiRouter; \ No newline at end of file diff --git a/src/routes/token.routes.ts b/src/routes/token.routes.ts new file mode 100644 index 0000000..21ed3b6 --- /dev/null +++ b/src/routes/token.routes.ts @@ -0,0 +1,10 @@ +import { Hono } from 'hono'; +import { loadTokens } from '../controllers/token.controller'; + +// Create a Hono router for token routes +const tokenRouter = new Hono(); + +// Register token routes +tokenRouter.post('/', loadTokens); + +export default tokenRouter; \ No newline at end of file diff --git a/src/routes/webhook.routes.ts b/src/routes/webhook.routes.ts new file mode 100644 index 0000000..cc8df83 --- /dev/null +++ b/src/routes/webhook.routes.ts @@ -0,0 +1,11 @@ +import { Hono } from 'hono'; +import { registerWebhook, tweetWebhook } from '../controllers/webhook.controller'; + +// Create a Hono router for webhook routes +const webhookRouter = new Hono(); + +// Register webhook routes +webhookRouter.post('/register', registerWebhook); +webhookRouter.post('/', tweetWebhook); + +export default webhookRouter; \ No newline at end of file diff --git a/src/services/twitter.service.ts b/src/services/twitter.service.ts new file mode 100644 index 0000000..4f930f8 --- /dev/null +++ b/src/services/twitter.service.ts @@ -0,0 +1,181 @@ +import axios from 'axios'; +import { env } from '../config/env'; +import { loadTokens, saveTokens } from '../utils/encryption'; +import { TwitterOAuthResponse, TwitterPostResponse } from '../types'; + +// ChainGPT API URL +const CHAINGPT_API_URL = 'https://api.chaingpt.org'; + +/** + * Get Twitter access token by using an existing refresh token + * @returns Promise containing the refreshed access token + */ +export const getAccessToken = async (): Promise => { + try { + const tokens = await loadTokens(env.ENCRYPTION_KEY); + + // Check if we need to refresh the token + try { + // Attempt to use the current token to see if it's still valid + await axios.get('https://api.twitter.com/2/users/me', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }); + + // If no error is thrown, token is still valid + return tokens.accessToken; + } catch (error) { + // Token expired, refresh it + const newTokens = await refreshAccessToken(tokens.refreshToken); + + // Save new tokens + await saveTokens(newTokens.accessToken, newTokens.refreshToken, env.ENCRYPTION_KEY); + + return newTokens.accessToken; + } + } catch (error) { + console.error('Error getting Twitter access token:', error); + throw new Error('Failed to get Twitter access token. Check if tokens are set up correctly.'); + } +}; + +/** + * Refresh Twitter access token using a refresh token + * @param refreshToken - The Twitter API refresh token + * @returns Promise with updated access and refresh tokens + */ +export const refreshAccessToken = async (refreshToken: string): Promise<{ + accessToken: string; + refreshToken: string; +}> => { + try { + const params = new URLSearchParams(); + params.append('refresh_token', refreshToken); + params.append('grant_type', 'refresh_token'); + params.append('client_id', env.CLIENT_ID); + + const response = await axios.post( + 'https://api.twitter.com/2/oauth2/token', + params, + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + auth: { + username: env.CLIENT_ID, + password: env.CLIENT_SECRET, + }, + } + ); + + return { + accessToken: response.data.access_token, + refreshToken: response.data.refresh_token || refreshToken, + }; + } catch (error) { + console.error('Error refreshing access token:', error); + throw new Error('Failed to refresh Twitter access token'); + } +}; + +/** + * Generate text for a tweet using ChainGPT + * @param prompt - The prompt to generate tweet content + * @returns Promise with the generated tweet text + */ +export const getTextForTweet = async (prompt: string): Promise => { + try { + const response = await axios.post( + `${CHAINGPT_API_URL}/completions`, + { + prompt, + max_tokens: 500, + }, + { + headers: { + 'Content-Type': 'application/json', + 'api-key': env.CHAINGPT_API_KEY, + }, + } + ); + + return response.data.text; + } catch (error) { + console.error('Error generating tweet text:', error); + throw new Error('Failed to generate tweet content using ChainGPT API'); + } +}; + +/** + * Post a tweet to Twitter + * @param accessToken - The Twitter API access token + * @param message - The message to tweet + * @returns Promise with the Twitter API response + */ +export const postTweet = async (accessToken: string, message: string): Promise => { + try { + const response = await axios.post( + 'https://api.twitter.com/2/tweets', + { + text: message, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + } + ); + + return response.data; + } catch (error) { + console.error('Error posting tweet:', error); + throw new Error('Failed to post tweet to Twitter'); + } +}; + +/** + * Generate a tweet based on prompt and post it to Twitter + * @param prompt - The prompt to generate tweet content + * @returns Promise with the Twitter API response and posted tweet + */ +export const generateAndPostTweet = async (prompt: string): Promise<{ + response: TwitterPostResponse; + tweet: string; +}> => { + try { + const tweet = await getTextForTweet(prompt); + const accessToken = await getAccessToken(); + const response = await postTweet(accessToken, tweet); + + console.log(`Tweet posted successfully: ${tweet}`); + + return { + response, + tweet, + }; + } catch (error) { + console.error('Error generating and posting tweet:', error); + throw error; + } +}; + +/** + * Upload and post a tweet directly with given message + * @param message - The message to tweet + * @returns Promise with the Twitter API response + */ +export const uploadTwitterPostTweet = async (message: string): Promise => { + try { + const accessToken = await getAccessToken(); + const response = await postTweet(accessToken, message); + + console.log(`Tweet posted successfully: ${message}`); + + return response; + } catch (error) { + console.error('Error posting tweet:', error); + throw error; + } +}; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..ed96f5b --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,88 @@ +/** + * Twitter API token interface + */ +export interface TwitterTokens { + accessToken: string; + refreshToken: string; +} + +/** + * Encrypted Twitter token interface + */ +export interface EncryptedTokens { + encryptedAccessToken: string; + encryptedRefreshToken: string; +} + +/** + * Twitter API OAuth response interface + */ +export interface TwitterOAuthResponse { + token_type: string; + expires_in: number; + access_token: string; + scope: string; + refresh_token?: string; +} + +/** + * Twitter API tweet post response interface + */ +export interface TwitterPostResponse { + data: { + id: string; + text: string; + }; +} + +/** + * Schedule configuration interface + */ +export interface ScheduleConfig { + config: { + persona: string; + maxLength: number; + timezone: string; + [key: string]: any; + }; + schedule: { + [timeKey: string]: { + type: string; + instruction: string; + [key: string]: any; + }; + }; +} + +/** + * Webhook registration request interface + */ +export interface WebhookRegistrationRequest { + url: string; +} + +/** + * Tweet webhook request interface + */ +export interface TweetWebhookRequest { + tweet: string; +} + +/** + * Token loading request interface + */ +export interface TokenLoadRequest { + accessToken: string; + refreshToken: string; +} + +/** + * API response interface for standardized responses + */ +export interface ApiResponse { + success: boolean; + message: string; + data?: T; + error?: string; +} + diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts new file mode 100644 index 0000000..3f1bb55 --- /dev/null +++ b/src/utils/encryption.ts @@ -0,0 +1,143 @@ +import { join, dirname } from 'path'; +import { env } from '../config/env'; +import { EncryptedTokens, TwitterTokens } from '../types'; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; + +// File path for storing encrypted tokens +const TOKENS_FILE_PATH = join(import.meta.dir, '../../data/tokens.json'); + +// Encryption settings +const ALGORITHM = 'AES-GCM'; // Using Web Crypto API algorithm name +const KEY_LENGTH = 32; // 256-bit key + +// Get salt and IV from environment variables +const SALT = new Uint8Array(Buffer.from(env.ENCRYPTION_SALT, 'hex')); +const IV = new Uint8Array(Buffer.from(env.IV, 'hex')); + +/** + * Convert a string to a key using PBKDF2 + * @param encryptionKey - The encryption key from environment + * @returns A Promise containing the derived key + */ +async function deriveKey(encryptionKey: string): Promise { + // Import the encryption key as raw material + const encoder = new TextEncoder(); + const keyMaterial = await crypto.subtle.importKey( + 'raw', + encoder.encode(encryptionKey), + 'PBKDF2', + false, + ['deriveKey'] + ); + + // Derive a key using PBKDF2 + return crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt: SALT, + iterations: 100000, + hash: 'SHA-256' + }, + keyMaterial, + { name: ALGORITHM, length: KEY_LENGTH * 8 }, + false, + ['encrypt', 'decrypt'] + ); +} + +/** + * Encrypt text data + * @param text - The text to encrypt + * @param encryptionKey - The encryption key + * @returns The encrypted text with authentication tag + */ +export async function encrypt(text: string, encryptionKey: string): Promise { + const key = await deriveKey(encryptionKey); + const encoder = new TextEncoder(); + const data = encoder.encode(text); + + // Encrypt the data + const encryptedBuffer = await crypto.subtle.encrypt( + { + name: ALGORITHM, + iv: IV + }, + key, + data + ); + + // Convert the encrypted buffer to base64 + return Buffer.from(encryptedBuffer).toString('base64'); +} + +/** + * Decrypt encrypted text + * @param encryptedText - The encrypted text (base64 encoded) + * @param encryptionKey - The encryption key + * @returns The decrypted text + */ +export async function decrypt(encryptedText: string, encryptionKey: string): Promise { + const key = await deriveKey(encryptionKey); + const encryptedBuffer = Buffer.from(encryptedText, 'base64'); + + // Decrypt the data + const decryptedBuffer = await crypto.subtle.decrypt( + { + name: ALGORITHM, + iv: IV + }, + key, + encryptedBuffer + ); + + // Convert the decrypted buffer to string + const decoder = new TextDecoder(); + return decoder.decode(decryptedBuffer); +} + +/** + * Save Twitter tokens to file system in encrypted format + * @param accessToken - Twitter API access token + * @param refreshToken - Twitter API refresh token + * @param encryptionKey - The encryption key + */ +export async function saveTokens( + accessToken: string, + refreshToken: string, + encryptionKey: string +): Promise { + const encryptedAccessToken = await encrypt(accessToken, encryptionKey); + const encryptedRefreshToken = await encrypt(refreshToken, encryptionKey); + + const tokens: EncryptedTokens = { + encryptedAccessToken, + encryptedRefreshToken + }; + + // Ensure directory exists + const dirPath = join(dirname(TOKENS_FILE_PATH)); + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { recursive: true }); + } + + writeFileSync(TOKENS_FILE_PATH, JSON.stringify(tokens, null, 2)); +} + +/** + * Load Twitter tokens from file system and decrypt them + * @param encryptionKey - The encryption key + * @returns The decrypted Twitter tokens + */ +export async function loadTokens(encryptionKey: string): Promise { + if (!existsSync(TOKENS_FILE_PATH)) { + throw new Error('Twitter tokens file not found. Please set up tokens first.'); + } + + const tokensData = readFileSync(TOKENS_FILE_PATH, 'utf8'); + const encryptedTokens: EncryptedTokens = JSON.parse(tokensData); + + const accessToken = await decrypt(encryptedTokens.encryptedAccessToken, encryptionKey); + const refreshToken = await decrypt(encryptedTokens.encryptedRefreshToken, encryptionKey); + + return { accessToken, refreshToken }; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b6a1343 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["bun-types"], + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true + } +} \ No newline at end of file From 97e7dc06c444bef060dc969f4c2a603581b07089 Mon Sep 17 00:00:00 2001 From: M Martinez <2624124+mmartinez@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:33:37 +0100 Subject: [PATCH 010/100] updating author --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f620a25..e39a20c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "format": "prettier --write 'src/**/*.ts'", "test": "bun test" }, - "author": "soroojshehryar01@gmail.com", + "author": "ChainGPT", "license": "ISC", "description": "", "dependencies": { From 56b77c83f02c7119ea75f5d2e0d947a3f82933a8 Mon Sep 17 00:00:00 2001 From: M Martinez <2624124+mmartinez@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:43:54 +0100 Subject: [PATCH 011/100] updating readme --- README.md | 165 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 142 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b5288c4..9e45e68 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ -# Twitter AI Agent +# ChainGPT Twitter AI Agent -A TypeScript-based Twitter bot that automatically generates and posts tweets on a schedule using AI. Built with Hono, TypeScript, and Bun runtime. +A TypeScript-based Twitter Agent that automatically generates and posts tweets on a schedule using AI. Built with Hono, TypeScript, and Bun runtime. + +## ๐Ÿš€ Project Overview + +This open-source project provides two key features for automated tweet generation and posting: + +- ๐Ÿ•’ **Scheduled Tweeting with Cron Jobs**: Define a schedule file containing prompts and specific times for tweet generation. A cron job processes the schedule, generates tweets based on the provided prompt, and automatically publishes them. +- ๐Ÿ”— **Automated Tweeting via ChainGPT Webhooks**: Register your webhook with the ChainGPT portal and subscribe to specific news categories. When ChainGPT publishes news in a subscribed category, the project receives a relevant tweet and posts it automatically. ## Features @@ -12,11 +19,30 @@ A TypeScript-based Twitter bot that automatically generates and posts tweets on - ๐Ÿš€ Ultra-fast Bun runtime for improved performance - ๐Ÿ“Š TypeScript for type safety and better developer experience -## Prerequisites +## ๐Ÿ›  Requirements - [Bun](https://bun.sh) 1.0+ runtime (faster alternative to Node.js) - Twitter API credentials (OAuth 2.0) -- ChainGPT API key +- A ChainGPT API key for authentication and access to the service +- Credits for tweet generation (each tweet consumes 1 credit) + +## ๐Ÿ”‘ How to Generate a ChainGPT API Key + +To use this project, follow these steps: + +1. **Access the API Dashboard** + - Go to the [ChainGPT API Dashboard](https://chaingpt.org/dashboard) + - Connect your crypto wallet to authenticate + +2. **Create an API Key** + - Click "Create New Key" + - Copy and securely store the secret phrase (required for authentication) + +3. **Purchase Credits** + - Visit [ChainGPT Credits](https://chaingpt.org/credits) + - Buy credits as each tweet generation consumes 1 credit + +Once you have your API key and credits, you're ready to start! ๐Ÿš€ ## Installation @@ -60,6 +86,111 @@ A TypeScript-based Twitter bot that automatically generates and posts tweets on CHAINGPT_API_KEY=your_chaingpt_api_key ``` +## ๐Ÿ”„ Working Steps + +Before running the project, follow these setup steps. + +### ๐Ÿ”‘ Load Twitter Access Tokens + +Before generating tweets, you need to load your Twitter Access Token and Refresh Token into the project. + +**Request:** +``` +POST {projectUrl}/api/tokens/ +``` + +**Body:** +```json +{ + "accessToken": "", + "refreshToken": "" +} +``` + +Once done, the project can authenticate and post tweets on your behalf. + +### ๐Ÿ” Tweeting Workflows + +#### ๐Ÿ“… Flow 1: Scheduled Tweeting via Cron Job + +1. **Define Your Schedule** + Create a `schedule.json` file in the following format: + + ```json + { + "14:30": "The future of AI", + "18:00": "Crypto markets are evolving" + } + ``` + - Time: Must be in UTC format (HH:MM) + - Prompt: The text used to generate the tweet + +2. **Run the Cron Job** + The cron job executes automatically at the scheduled times, generating tweets using ChainGPT's tweet generation feature and publishing them. + +#### ๐Ÿ”” Flow 2: Tweeting via ChainGPT News Categories + +1. **Get Available Categories** + Retrieve all available categories and your subscribed ones. + + **Request:** + ``` + GET https://webapi.chaingpt.org/category-subscription/ + ``` + + **Headers:** + ```json + { + "api-key": "" + } + ``` + This returns a list of categories along with your current subscriptions. + +2. **Subscribe to Categories** + To subscribe, send a POST request with the category IDs. + + **Request:** + ``` + POST https://webapi.chaingpt.org/category-subscription/subscribe + ``` + + **Headers:** + ```json + { + "api-key": "" + } + ``` + + **Body:** + ```json + { + "categoryIds": [2, 3] + } + ``` + (IDs 2 and 3 correspond to the categories you want to tweet about.) + +3. **Register Webhook with ChainGPT** + Register your webhook with ChainGPT to receive updates. + + **Request:** + ``` + POST {base_url_of_your_project}/api/webhook/register + ``` + + **Body:** + ```json + { + "url": "{base_url_of_your_project}/api/webhook/" + } + ``` + + **How It Works:** + - This registers your webhook URL with ChainGPT + - When ChainGPT publishes news in your subscribed categories, it sends a POST request to your webhook + - The project processes the tweet and posts it to Twitter + +With this setup, the entire process is automated, ensuring real-time tweet updates for the latest news in your subscribed categories! + ## Usage ### Development Mode @@ -93,24 +224,6 @@ bun start - Receive a tweet from a webhook and post it - Body: `{ "tweet": "Your tweet content" }` -## Tweet Scheduling - -The application uses a schedule defined in `data/schedule.json` to post tweets at specific times. The format is: - -```json -{ - "HH:MM": "Prompt for generating tweet content" -} -``` - -For example: -```json -{ - "09:00": "Create a tweet about the latest crypto news", - "15:30": "Generate a meme about blockchain technology" -} -``` - ## Project Structure ``` @@ -172,5 +285,11 @@ ISC ## Author -soroojshehryar01@gmail.com +ChainGPT + +## ๐Ÿ“ง Support + +If you encounter any issues or need assistance, feel free to reach out via GitHub issues. + +๐Ÿ‘จโ€๐Ÿ’ป Happy Coding! ๐Ÿš€ From e3bf1219b18a72963fa8ce4b72cffa3dd8b4e2ae Mon Sep 17 00:00:00 2001 From: M Martinez <2624124+mmartinez@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:46:22 +0100 Subject: [PATCH 012/100] Fixing urls --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e45e68..9688213 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This open-source project provides two key features for automated tweet generatio To use this project, follow these steps: 1. **Access the API Dashboard** - - Go to the [ChainGPT API Dashboard](https://chaingpt.org/dashboard) + - Go to the [ChainGPT API Dashboard](https://app.chaingpt.org/apidashboard) - Connect your crypto wallet to authenticate 2. **Create an API Key** @@ -39,7 +39,7 @@ To use this project, follow these steps: - Copy and securely store the secret phrase (required for authentication) 3. **Purchase Credits** - - Visit [ChainGPT Credits](https://chaingpt.org/credits) + - Visit [ChainGPT Credits](https://app.chaingpt.org/addcredits) - Buy credits as each tweet generation consumes 1 credit Once you have your API key and credits, you're ready to start! ๐Ÿš€ From 9d63f6a49e6970d7356b444a1da86d4f8dd149f6 Mon Sep 17 00:00:00 2001 From: M Martinez <2624124+mmartinez@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:51:38 +0100 Subject: [PATCH 013/100] removing comment --- src/controllers/webhook.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts index bdaba26..947ddf6 100644 --- a/src/controllers/webhook.controller.ts +++ b/src/controllers/webhook.controller.ts @@ -4,7 +4,7 @@ import { env } from '../config/env'; import { uploadTwitterPostTweet } from '../services/twitter.service'; import { ApiResponse, TweetWebhookRequest, WebhookRegistrationRequest } from '../types'; -// ChainGPT API URL - hardcoded as it shouldn't change +// ChainGPT API URL const CHAINGPT_API_URL = 'https://api.chaingpt.org'; /** From 9829bb67f09edfbf48fcc98cb31f49f33d351d16 Mon Sep 17 00:00:00 2001 From: sorooj Date: Fri, 7 Mar 2025 10:48:19 +0500 Subject: [PATCH 014/100] changes made for consistency with chaingpt --- bun.lock | 342 ++++++++++++++++++++++++++ src/controllers/webhook.controller.ts | 9 +- src/services/twitter.service.ts | 10 +- 3 files changed, 353 insertions(+), 8 deletions(-) create mode 100644 bun.lock diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..4d0d214 --- /dev/null +++ b/bun.lock @@ -0,0 +1,342 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "twitter-ai-agent", + "dependencies": { + "axios": "^1.7.9", + "hono": "^4.1.5", + "node-cron": "^3.0.3", + "zod": "^3.22.4", + }, + "devDependencies": { + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "bun-types": "^1.2.4", + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "typescript": "^5.4.2", + }, + }, + }, + "packages": { + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.4.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], + + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], + + "@types/node-cron": ["@types/node-cron@3.0.11", "", {}, "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="], + + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.18.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/type-utils": "7.18.0", "@typescript-eslint/utils": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.8.1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + + "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=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], + + "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-cron": ["node-cron@3.0.3", "", { "dependencies": { "uuid": "8.3.2" } }, "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + } +} diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts index 947ddf6..677f1fe 100644 --- a/src/controllers/webhook.controller.ts +++ b/src/controllers/webhook.controller.ts @@ -5,7 +5,7 @@ import { uploadTwitterPostTweet } from '../services/twitter.service'; import { ApiResponse, TweetWebhookRequest, WebhookRegistrationRequest } from '../types'; // ChainGPT API URL -const CHAINGPT_API_URL = 'https://api.chaingpt.org'; +const CHAINGPT_API_URL = 'https://webapi.chaingpt.org'; /** * Register a webhook with ChainGPT @@ -67,12 +67,15 @@ export const tweetWebhook = async (c: Context): Promise => { }, 400); } - const response = await uploadTwitterPostTweet(tweet); + let tweetText = tweet; + // let tweetText = tweet.slice(0, 270); + + const response = await uploadTwitterPostTweet(tweetText); return c.json({ success: true, message: 'Tweet posted successfully', - data: { tweet, response } + data: { tweetText, response } }); } catch (error) { console.error('Error posting tweet via webhook:', error); diff --git a/src/services/twitter.service.ts b/src/services/twitter.service.ts index 4f930f8..af81a7e 100644 --- a/src/services/twitter.service.ts +++ b/src/services/twitter.service.ts @@ -4,7 +4,7 @@ import { loadTokens, saveTokens } from '../utils/encryption'; import { TwitterOAuthResponse, TwitterPostResponse } from '../types'; // ChainGPT API URL -const CHAINGPT_API_URL = 'https://api.chaingpt.org'; +const CHAINGPT_API_URL = 'https://webapi.chaingpt.org'; /** * Get Twitter access token by using an existing refresh token @@ -87,10 +87,9 @@ export const refreshAccessToken = async (refreshToken: string): Promise<{ export const getTextForTweet = async (prompt: string): Promise => { try { const response = await axios.post( - `${CHAINGPT_API_URL}/completions`, + `${CHAINGPT_API_URL}/tweet-generator`, { prompt, - max_tokens: 500, }, { headers: { @@ -99,8 +98,9 @@ export const getTextForTweet = async (prompt: string): Promise => { }, } ); - - return response.data.text; + let tweetText = response.data.tweet + // let tweetText = response.data.tweet.slice(0, 270); // use this for base twitter api key + return tweetText; } catch (error) { console.error('Error generating tweet text:', error); throw new Error('Failed to generate tweet content using ChainGPT API'); From a4274b930de264826f6f4a3570d7d34d07cdb42b Mon Sep 17 00:00:00 2001 From: sorooj Date: Fri, 7 Mar 2025 10:55:30 +0500 Subject: [PATCH 015/100] removing lock --- bun.lock | 342 ------------------------------------------------------- 1 file changed, 342 deletions(-) delete mode 100644 bun.lock diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 4d0d214..0000000 --- a/bun.lock +++ /dev/null @@ -1,342 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "twitter-ai-agent", - "dependencies": { - "axios": "^1.7.9", - "hono": "^4.1.5", - "node-cron": "^3.0.3", - "zod": "^3.22.4", - }, - "devDependencies": { - "@types/node-cron": "^3.0.11", - "@typescript-eslint/eslint-plugin": "^7.3.1", - "@typescript-eslint/parser": "^7.3.1", - "bun-types": "^1.2.4", - "eslint": "^8.57.0", - "prettier": "^3.2.5", - "typescript": "^5.4.2", - }, - }, - }, - "packages": { - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.4.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], - - "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], - - "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/node-cron": ["@types/node-cron@3.0.11", "", {}, "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="], - - "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.18.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/type-utils": "7.18.0", "@typescript-eslint/utils": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], - - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - - "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.8.1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], - - "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], - - "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=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], - - "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - - "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], - - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], - - "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], - - "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], - - "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], - - "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "node-cron": ["node-cron@3.0.3", "", { "dependencies": { "uuid": "8.3.2" } }, "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - - "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], - - "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], - - "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - } -} From cf58e4c4c2fc42b08d3f7fc793337d995cd12d8f Mon Sep 17 00:00:00 2001 From: Ilan Rakhmanov <26630467+ceoguy@users.noreply.github.com> Date: Wed, 9 Apr 2025 17:07:38 -0700 Subject: [PATCH 016/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9688213..098efed 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This open-source project provides two key features for automated tweet generatio ## Features -- ๐Ÿค– AI-powered tweet generation using ChainGPT +- ๐Ÿค– AI-powered tweet generation using ChainGPT's "Web3 LLM" API. - ๐Ÿ“… Scheduled tweets based on configurable prompts - ๐Ÿ”’ Secure token storage with encryption - ๐ŸŒ Webhook support for external integrations From 061529cf28872f183da7fa71aea77669527f3714 Mon Sep 17 00:00:00 2001 From: Ilan Rakhmanov <26630467+ceoguy@users.noreply.github.com> Date: Wed, 9 Apr 2025 17:19:37 -0700 Subject: [PATCH 017/100] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 098efed..bd6ec66 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ChainGPT Twitter AI Agent +# AgenticOS - Twitter AI Agent for Web3 - by ChainGPT A TypeScript-based Twitter Agent that automatically generates and posts tweets on a schedule using AI. Built with Hono, TypeScript, and Bun runtime. @@ -31,7 +31,7 @@ This open-source project provides two key features for automated tweet generatio To use this project, follow these steps: 1. **Access the API Dashboard** - - Go to the [ChainGPT API Dashboard](https://app.chaingpt.org/apidashboard) + - Go to the [Crypto AI Hub - API Dashboard](https://app.chaingpt.org/apidashboard) - Connect your crypto wallet to authenticate 2. **Create an API Key** From 555c8f90dbe508aed153133368e5dd7b40348587 Mon Sep 17 00:00:00 2001 From: Ilan Rakhmanov <26630467+ceoguy@users.noreply.github.com> Date: Sun, 13 Apr 2025 14:45:47 -0700 Subject: [PATCH 018/100] Update README.md --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bd6ec66..f63cc40 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ -# AgenticOS - Twitter AI Agent for Web3 - by ChainGPT +# AgenticOS โ€“ Your AI Agent for Web3 on X (Twitter) - by ChainGPT -A TypeScript-based Twitter Agent that automatically generates and posts tweets on a schedule using AI. Built with Hono, TypeScript, and Bun runtime. +Launch your own intelligent AI Agent on X (formerly Twitter) โ€” purpose-built for Web3. +From real-time market research and breaking crypto news to token analysis and community engagement, AgenticOS automates it all. +Empower your digital presence with 24/7 AI performance, tailored to your goals. + +Demo: https://x.com/ChainGPTAI + +*** A TypeScript-based Twitter Agent that automatically generates and posts tweets on a schedule using AI. Built with Hono, TypeScript, and Bun runtime. ## ๐Ÿš€ Project Overview From 3f4569b87395a117adab29aa18770dab598f1723 Mon Sep 17 00:00:00 2001 From: Ilan Rakhmanov <26630467+ceoguy@users.noreply.github.com> Date: Sun, 13 Apr 2025 14:48:31 -0700 Subject: [PATCH 019/100] Update README.md --- README.md | 359 +++++++++++++++++++----------------------------------- 1 file changed, 126 insertions(+), 233 deletions(-) diff --git a/README.md b/README.md index f63cc40..58fc091 100644 --- a/README.md +++ b/README.md @@ -1,301 +1,194 @@ -# AgenticOS โ€“ Your AI Agent for Web3 on X (Twitter) - by ChainGPT +# AgenticOS โ€“ Your AI Agent for Web3 on X (Twitter) -Launch your own intelligent AI Agent on X (formerly Twitter) โ€” purpose-built for Web3. -From real-time market research and breaking crypto news to token analysis and community engagement, AgenticOS automates it all. -Empower your digital presence with 24/7 AI performance, tailored to your goals. +**Built by ChainGPT** -Demo: https://x.com/ChainGPTAI +AgenticOS lets you effortlessly create and deploy your own intelligent AI agent on X (formerly Twitter)โ€”purpose-built for the Web3 ecosystem. Automate tasks like real-time market research, breaking crypto news, token analysis, and community engagement, enhancing your digital presence with 24/7 AI-driven insights. -*** A TypeScript-based Twitter Agent that automatically generates and posts tweets on a schedule using AI. Built with Hono, TypeScript, and Bun runtime. +๐Ÿ“Œ **Live Demo**: [ChainGPT AI on X](https://x.com/ChainGPTAI) -## ๐Ÿš€ Project Overview +--- -This open-source project provides two key features for automated tweet generation and posting: +## ๐Ÿš€ Overview -- ๐Ÿ•’ **Scheduled Tweeting with Cron Jobs**: Define a schedule file containing prompts and specific times for tweet generation. A cron job processes the schedule, generates tweets based on the provided prompt, and automatically publishes them. -- ๐Ÿ”— **Automated Tweeting via ChainGPT Webhooks**: Register your webhook with the ChainGPT portal and subscribe to specific news categories. When ChainGPT publishes news in a subscribed category, the project receives a relevant tweet and posts it automatically. +AgenticOS is a TypeScript-based AI agent that automates tweet generation and publishing, leveraging ChainGPT's advanced Web3 LLM API and the ultra-fast Bun runtime. Built for ease of integration and secure performance. -## Features +### ๐Ÿ”ฅ Key Features -- ๐Ÿค– AI-powered tweet generation using ChainGPT's "Web3 LLM" API. -- ๐Ÿ“… Scheduled tweets based on configurable prompts -- ๐Ÿ”’ Secure token storage with encryption -- ๐ŸŒ Webhook support for external integrations -- ๐Ÿ”„ Automatic token refresh for Twitter API -- ๐Ÿš€ Ultra-fast Bun runtime for improved performance -- ๐Ÿ“Š TypeScript for type safety and better developer experience +- **AI-powered Tweet Generation** using ChainGPT's Web3 LLM +- **Scheduled Automated Tweets** via configurable Cron jobs +- **Webhook Integration** with ChainGPT for automatic real-time updates +- **Secure Token Storage** with encryption +- **Automatic Twitter Token Refresh** (OAuth 2.0) +- **TypeScript** for enhanced developer experience and type safety +- **Ultra-fast Bun Runtime** for optimal performance -## ๐Ÿ›  Requirements +--- -- [Bun](https://bun.sh) 1.0+ runtime (faster alternative to Node.js) -- Twitter API credentials (OAuth 2.0) -- A ChainGPT API key for authentication and access to the service -- Credits for tweet generation (each tweet consumes 1 credit) +## โš™๏ธ Requirements -## ๐Ÿ”‘ How to Generate a ChainGPT API Key +- [Bun Runtime](https://bun.sh) (v1.0 or newer) +- Twitter API credentials (OAuth 2.0) +- ChainGPT API Key ([Get one here](https://app.chaingpt.org/apidashboard)) +- ChainGPT Credits ([Purchase credits](https://app.chaingpt.org/addcredits)) -To use this project, follow these steps: +Each generated tweet consumes 1 ChainGPT credit. -1. **Access the API Dashboard** - - Go to the [Crypto AI Hub - API Dashboard](https://app.chaingpt.org/apidashboard) - - Connect your crypto wallet to authenticate +--- -2. **Create an API Key** - - Click "Create New Key" - - Copy and securely store the secret phrase (required for authentication) +## ๐Ÿ”‘ Quick Start -3. **Purchase Credits** - - Visit [ChainGPT Credits](https://app.chaingpt.org/addcredits) - - Buy credits as each tweet generation consumes 1 credit +### Step 1: Clone and Set Up +```bash +git clone https://github.com/yourusername/twitter-ai-agent.git +cd twitter-ai-agent -Once you have your API key and credits, you're ready to start! ๐Ÿš€ +# Install Bun runtime +curl -fsSL https://bun.sh/install | bash -## Installation +# Install project dependencies +bun install -1. Clone the repository: - ```bash - git clone https://github.com/yourusername/twitter-ai-agent.git - cd twitter-ai-agent - ``` +# Configure your environment +cp .env.example .env +``` -2. Install Bun if you don't have it already: - ```bash - curl -fsSL https://bun.sh/install | bash - ``` +### Step 2: Configure `.env` -3. Install dependencies with Bun: - ```bash - bun install - ``` +Update `.env` with your details: +```bash +PORT=8000 +NODE_ENV=development -4. Create a `.env` file based on the example: - ```bash - cp .env.example .env - ``` +CLIENT_ID=your_twitter_client_id +CLIENT_SECRET=your_twitter_client_secret -5. Fill in your environment variables in the `.env` file: - ``` - # Server Configuration - PORT=8000 - NODE_ENV=development +ENCRYPTION_KEY=your_32_character_encryption_key +ENCRYPTION_SALT=your_hex_encryption_salt +IV=your_hex_initialization_vector - # Twitter API Credentials - CLIENT_ID=your_twitter_client_id - CLIENT_SECRET=your_twitter_client_secret +CHAINGPT_API_KEY=your_chaingpt_api_key +``` - # Encryption Settings - ENCRYPTION_KEY=your_32_character_encryption_key - ENCRYPTION_SALT=your_hex_encryption_salt - IV=your_hex_initialization_vector +--- - # ChainGPT API - CHAINGPT_API_KEY=your_chaingpt_api_key - ``` +## ๐Ÿšฉ Usage -## ๐Ÿ”„ Working Steps +### Development Mode +```bash +bun dev +``` -Before running the project, follow these setup steps. +### Production Mode +```bash +bun build +bun start +``` -### ๐Ÿ”‘ Load Twitter Access Tokens +--- -Before generating tweets, you need to load your Twitter Access Token and Refresh Token into the project. +## ๐Ÿ“… Automated Tweeting Workflows -**Request:** -``` -POST {projectUrl}/api/tokens/ -``` +### Workflow 1: Scheduled Tweeting (Cron) -**Body:** +Define your schedule in `data/schedule.json`: ```json { - "accessToken": "", - "refreshToken": "" + "14:30": "The future of AI in Web3", + "18:00": "Crypto markets update" } ``` -Once done, the project can authenticate and post tweets on your behalf. - -### ๐Ÿ” Tweeting Workflows - -#### ๐Ÿ“… Flow 1: Scheduled Tweeting via Cron Job - -1. **Define Your Schedule** - Create a `schedule.json` file in the following format: - - ```json - { - "14:30": "The future of AI", - "18:00": "Crypto markets are evolving" - } - ``` - - Time: Must be in UTC format (HH:MM) - - Prompt: The text used to generate the tweet - -2. **Run the Cron Job** - The cron job executes automatically at the scheduled times, generating tweets using ChainGPT's tweet generation feature and publishing them. - -#### ๐Ÿ”” Flow 2: Tweeting via ChainGPT News Categories - -1. **Get Available Categories** - Retrieve all available categories and your subscribed ones. - - **Request:** - ``` - GET https://webapi.chaingpt.org/category-subscription/ - ``` - - **Headers:** - ```json - { - "api-key": "" - } - ``` - This returns a list of categories along with your current subscriptions. +Tweets are auto-generated and posted according to this schedule (UTC). -2. **Subscribe to Categories** - To subscribe, send a POST request with the category IDs. - - **Request:** - ``` - POST https://webapi.chaingpt.org/category-subscription/subscribe - ``` - - **Headers:** - ```json - { - "api-key": "" - } - ``` - - **Body:** - ```json - { - "categoryIds": [2, 3] - } - ``` - (IDs 2 and 3 correspond to the categories you want to tweet about.) - -3. **Register Webhook with ChainGPT** - Register your webhook with ChainGPT to receive updates. - - **Request:** - ``` - POST {base_url_of_your_project}/api/webhook/register - ``` - - **Body:** - ```json - { - "url": "{base_url_of_your_project}/api/webhook/" - } - ``` - - **How It Works:** - - This registers your webhook URL with ChainGPT - - When ChainGPT publishes news in your subscribed categories, it sends a POST request to your webhook - - The project processes the tweet and posts it to Twitter - -With this setup, the entire process is automated, ensuring real-time tweet updates for the latest news in your subscribed categories! - -## Usage - -### Development Mode +### Workflow 2: ChainGPT Webhook for Live News +**Subscribe to Categories:** +- Get available categories: ```bash -bun dev +GET https://webapi.chaingpt.org/category-subscription/ ``` -### Production Mode - +- Subscribe to categories: ```bash -bun build -bun start -``` +POST https://webapi.chaingpt.org/category-subscription/subscribe -## API Endpoints +Body: { "categoryIds": [2, 3] } +``` -### Token Management +**Register Webhook:** -- **POST /api/tokens** - - Load Twitter API tokens - - Body: `{ "accessToken": "your_access_token", "refreshToken": "your_refresh_token" }` +Register your webhook to automatically post updates: +```bash +POST {your_project_url}/api/webhook/register -### Webhook Management +Body: { "url": "{your_project_url}/api/webhook/" } +``` -- **POST /api/webhook/register** - - Register a webhook with ChainGPT - - Body: `{ "url": "https://your-webhook-url.com" }` +AgenticOS will automatically post tweets from ChainGPT news updates. -- **POST /api/webhook** - - Receive a tweet from a webhook and post it - - Body: `{ "tweet": "Your tweet content" }` +--- -## Project Structure +## ๐Ÿ“š Project Structure ``` twitter-ai-agent/ -โ”œโ”€โ”€ data/ # Data storage -โ”‚ โ””โ”€โ”€ schedule.json # Tweet schedule configuration -โ”œโ”€โ”€ src/ # Source code -โ”‚ โ”œโ”€โ”€ config/ # Configuration -โ”‚ โ”‚ โ””โ”€โ”€ env.ts # Environment variables validation -โ”‚ โ”œโ”€โ”€ controllers/ # API controllers -โ”‚ โ”‚ โ”œโ”€โ”€ token.controller.ts -โ”‚ โ”‚ โ””โ”€โ”€ webhook.controller.ts -โ”‚ โ”œโ”€โ”€ jobs/ # Scheduled jobs -โ”‚ โ”‚ โ””โ”€โ”€ tweet.job.ts # Tweet scheduling -โ”‚ โ”œโ”€โ”€ routes/ # API routes -โ”‚ โ”‚ โ”œโ”€โ”€ index.ts # Main router -โ”‚ โ”‚ โ”œโ”€โ”€ token.routes.ts -โ”‚ โ”‚ โ””โ”€โ”€ webhook.routes.ts -โ”‚ โ”œโ”€โ”€ services/ # Business logic -โ”‚ โ”‚ โ””โ”€โ”€ twitter.service.ts -โ”‚ โ”œโ”€โ”€ types/ # TypeScript type definitions -โ”‚ โ”‚ โ””โ”€โ”€ index.ts -โ”‚ โ”œโ”€โ”€ utils/ # Utility functions -โ”‚ โ”‚ โ””โ”€โ”€ encryption.ts # Token encryption -โ”‚ โ””โ”€โ”€ index.ts # Application entry point -โ”œโ”€โ”€ .env # Environment variables -โ”œโ”€โ”€ .env.example # Example environment variables -โ”œโ”€โ”€ package.json # Dependencies and scripts -โ””โ”€โ”€ tsconfig.json # TypeScript configuration +โ”œโ”€โ”€ data/ +โ”‚ โ””โ”€โ”€ schedule.json +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ”œโ”€โ”€ controllers/ +โ”‚ โ”œโ”€โ”€ jobs/ +โ”‚ โ”œโ”€โ”€ routes/ +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”œโ”€โ”€ types/ +โ”‚ โ”œโ”€โ”€ utils/ +โ”‚ โ””โ”€โ”€ index.ts +โ”œโ”€โ”€ .env.example +โ”œโ”€โ”€ package.json +โ””โ”€โ”€ tsconfig.json ``` -## Why Bun? +--- -This project uses Bun instead of Node.js for several key benefits: +## ๐ŸŒ Why Choose Bun? -- **Improved Performance**: Significantly faster startup and execution times -- **Built-in TypeScript Support**: No need for separate compilation steps -- **Modern JavaScript Features**: First-class support for ESM and top-level await -- **Simplified Development**: Built-in tools for testing, bundling, and running -- **Compatible with Node.js**: Works with most npm packages +- ๐Ÿš€ **Superior Performance**: Faster execution & startup +- ๐Ÿ›  **Built-in TypeScript & ESM Support** +- ๐ŸŽฏ **Simplified Development**: Integrated tools for testing & bundling +- ๐Ÿ“ฆ **Compatible with npm packages** -## Security +--- -- All Twitter tokens are encrypted using Web Crypto API -- Environment variables are validated at startup -- Proper error handling throughout the application +## ๐Ÿ” Security -## Contributing +- Secure encryption of Twitter tokens +- Environment variable validation +- Robust error handling -1. Fork the repository -2. Create your feature branch: `git checkout -b feature/my-new-feature` -3. Commit your changes: `git commit -am 'Add some feature'` -4. Push to the branch: `git push origin feature/my-new-feature` -5. Submit a pull request +--- -## License +## ๐Ÿค Contributing -ISC +Contributions are welcome! Follow these steps: -## Author +1. Fork this repository. +2. Create a branch: `git checkout -b feature/my-new-feature` +3. Commit changes: `git commit -am 'Add feature'` +4. Push changes: `git push origin feature/my-new-feature` +5. Open a Pull Request. -ChainGPT +--- -## ๐Ÿ“ง Support +## ๐Ÿ“œ License + +**ISC** -If you encounter any issues or need assistance, feel free to reach out via GitHub issues. +## ๐Ÿง‘โ€๐Ÿ’ป Author + +**ChainGPT** + +## ๐Ÿ“ง Support -๐Ÿ‘จโ€๐Ÿ’ป Happy Coding! ๐Ÿš€ +Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-agent/issues). +๐Ÿš€ **Happy Coding!** From cbf3f736fac8bac0627ce54d86cb13620e104af3 Mon Sep 17 00:00:00 2001 From: Ilan Rakhmanov <26630467+ceoguy@users.noreply.github.com> Date: Sun, 13 Apr 2025 14:51:20 -0700 Subject: [PATCH 020/100] Update .env.example --- .env.example | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index a3ba9c7..3fb4cb4 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,26 @@ -# Server Configuration +# ============================= +# ๐Ÿš€ Server Configuration +# ============================= PORT=8000 -NODE_ENV=development +NODE_ENV=development # 'production' or 'development' -# Twitter API Credentials -CLIENT_ID=your_twitter_client_id -CLIENT_SECRET=your_twitter_client_secret +# ============================= +# ๐Ÿ”‘ Twitter API Credentials (OAuth 2.0) +# Obtain these from Twitter Developer Portal +# ============================= +TWITTER_CLIENT_ID=your_twitter_client_id +TWITTER_CLIENT_SECRET=your_twitter_client_secret -# Encryption Settings -ENCRYPTION_KEY=your_32_character_encryption_key -ENCRYPTION_SALT=your_hex_encryption_salt -IV=your_hex_initialization_vector +# ============================= +# ๐Ÿ” Encryption Settings +# (Use secure, randomly-generated values) +# ============================= +ENCRYPTION_KEY=your_32_characters_encryption_key +ENCRYPTION_SALT=your_hex_encoded_salt +ENCRYPTION_IV=your_hex_encoded_initialization_vector -# ChainGPT API -CHAINGPT_API_KEY=your_chaingpt_api_key \ No newline at end of file +# ============================= +# ๐Ÿค– ChainGPT API Configuration +# Obtain your API key from: https://app.chaingpt.org/apidashboard +# ============================= +CHAINGPT_API_KEY=your_chaingpt_api_key From 1b5ef00c9a21184b7a6b2beaa3d4a03045dc4882 Mon Sep 17 00:00:00 2001 From: sorooj Date: Tue, 15 Apr 2025 19:47:52 +0500 Subject: [PATCH 021/100] readme update --- README.md | 210 +++++++++++++++- bun.lock | 342 ++++++++++++++++++++++++++ src/config/env.ts | 66 ++--- src/controllers/token.controller.ts | 60 +++-- src/controllers/webhook.controller.ts | 142 ++++++----- src/index.ts | 59 ++--- src/routes/token.routes.ts | 8 +- src/services/twitter.service.ts | 276 +++++++++++---------- src/utils/encryption.ts | 198 ++++++++------- 9 files changed, 976 insertions(+), 385 deletions(-) create mode 100644 bun.lock diff --git a/README.md b/README.md index 58fc091..dd9bab6 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,10 @@ Each generated tweet consumes 1 ChainGPT credit. ## ๐Ÿ”‘ Quick Start ### Step 1: Clone and Set Up + ```bash -git clone https://github.com/yourusername/twitter-ai-agent.git -cd twitter-ai-agent +git clone https://github.com/ChainGPT-org/AgenticOS.git +cd AgenticOS # Install Bun runtime curl -fsSL https://bun.sh/install | bash @@ -55,16 +56,17 @@ cp .env.example .env ### Step 2: Configure `.env` Update `.env` with your details: + ```bash PORT=8000 NODE_ENV=development -CLIENT_ID=your_twitter_client_id -CLIENT_SECRET=your_twitter_client_secret +TWITTER_CLIENT_ID=your_twitter_client_id +TWITTER_CLIENT_SECRET=your_twitter_client_secret ENCRYPTION_KEY=your_32_character_encryption_key ENCRYPTION_SALT=your_hex_encryption_salt -IV=your_hex_initialization_vector +ENCRYPTION_IV=your_hex_initialization_vector CHAINGPT_API_KEY=your_chaingpt_api_key ``` @@ -73,12 +75,8 @@ CHAINGPT_API_KEY=your_chaingpt_api_key ## ๐Ÿšฉ Usage -### Development Mode -```bash -bun dev -``` - ### Production Mode + ```bash bun build bun start @@ -86,11 +84,199 @@ bun start --- +## Provide Twitter Access and Refresh Tokens + +### Add tokens to app + +```bash +# Add Twitter tokens to the application +POST /api/tokens + +# Request body +{ + "accessToken": "your_access_token", + "refreshToken": "your_refresh_token" +} +``` + +### Generate access and refresh tokens + +If you require quick code to generate twitter tokens then use below code + +```bash +# Imports +import express from "express"; +import axios from "axios"; +import crypto from "crypto"; +import querystring from "querystring"; +import session from "express-session"; +import { Buffer } from "buffer"; +``` + +after downloading and importing packages we need to make some necessary configurations + +```bash +# congigurations +type Request = express.Request; +type Response = express.Response; + +declare module "express-session" { + interface Session { + codeVerifier: string; + } +} + +interface TwitterTokens { + access_token: string; + refresh_token: string; +} + +# Configuration +const config = { + clientId: "your client id", + clientSecret: "your client secret", + redirectUri: "redirect url", + port: 8000, + sessionSecret: "session secret", +}; + +# Initialize Express app +const app = express(); + +# Session middleware +app.use( + session({ + secret: config.sessionSecret, + resave: false, + saveUninitialized: true, + }) +); + +``` + +Now we setup generatePKCE + +```bash +# Generate PKCE code verifier and challenge +const generatePKCE = (): { codeVerifier: string; codeChallenge: string } => { + const codeVerifier = crypto.randomBytes(32).toString("base64url"); + const codeChallenge = crypto + .createHash("sha256") + .update(codeVerifier) + .digest("base64url"); + return { codeVerifier, codeChallenge }; +}; +``` + +Following login endpoint can be used to login to twitter, after that login tokens will be generated in callcaback endpoint + +```bash +# Login route - initiates OAuth flow +app.get("/login", (req: Request, res: Response): void => { + const { codeVerifier, codeChallenge } = generatePKCE(); + const state = crypto.randomBytes(16).toString("hex"); + + # Store code verifier in session + req.session.codeVerifier = codeVerifier; + + const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify( + { + response_type: "code", + client_id: config.clientId, + redirect_uri: config.redirectUri, + scope: "tweet.read users.read tweet.write offline.access", + state, + code_challenge: codeChallenge, + code_challenge_method: "S256", + } + )}`; + + res.redirect(authorizationUrl); +}); +``` + +follwoing callback will be called after /login + +```bash +app.get("/callback", async (req: Request, res: Response): Promise => { + const code = req.query.code as string; + const codeVerifier = req.session.codeVerifier; + + if (!code || !codeVerifier) { + res.status(400).send("Authorization failed: Missing code or verifier"); + return; + } + + const basicAuth = Buffer.from( + `${config.clientId}:${config.clientSecret}` + ).toString("base64"); + + try { + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + querystring.stringify({ + code, + client_id: config.clientId, + redirect_uri: config.redirectUri, + code_verifier: codeVerifier, + grant_type: "authorization_code", + }), + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${basicAuth}`, + }, + } + ); + + const { access_token, refresh_token } = response.data; + console.log( + `Access and refresh tokens received: ${JSON.stringify( + { access_token, refresh_token }, + null, + 2 + )}` + ); + res.send( + `Access and refresh tokens received: ${JSON.stringify( + { access_token, refresh_token }, + null, + 2 + )}` + ); + } catch (error: any) { + if (axios.isAxiosError(error)) { + res + .status(500) + .send( + `Error during the token exchange: ${JSON.stringify( + error.response?.data || error.message + )}` + ); + } else { + res.status(500).send("An unexpected error occurred"); + } + } +}); +``` + +Starting server + +```bash +# Start the server +app.listen(config.port, () => { + console.log( + `Access and Refresh Token Generator listening on port ${config.port}` + ); +}); +``` + ## ๐Ÿ“… Automated Tweeting Workflows ### Workflow 1: Scheduled Tweeting (Cron) Define your schedule in `data/schedule.json`: + ```json { "14:30": "The future of AI in Web3", @@ -103,12 +289,15 @@ Tweets are auto-generated and posted according to this schedule (UTC). ### Workflow 2: ChainGPT Webhook for Live News **Subscribe to Categories:** + - Get available categories: + ```bash GET https://webapi.chaingpt.org/category-subscription/ ``` - Subscribe to categories: + ```bash POST https://webapi.chaingpt.org/category-subscription/subscribe @@ -118,6 +307,7 @@ Body: { "categoryIds": [2, 3] } **Register Webhook:** Register your webhook to automatically post updates: + ```bash POST {your_project_url}/api/webhook/register diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..58b3b6b --- /dev/null +++ b/bun.lock @@ -0,0 +1,342 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "twitter-ai-agent", + "dependencies": { + "axios": "^1.7.9", + "hono": "^4.1.5", + "node-cron": "^3.0.3", + "zod": "^3.22.4", + }, + "devDependencies": { + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "bun-types": "^1.2.4", + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "typescript": "^5.4.2", + }, + }, + }, + "packages": { + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.5.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], + + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="], + + "@types/node-cron": ["@types/node-cron@3.0.11", "", {}, "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="], + + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.18.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/type-utils": "7.18.0", "@typescript-eslint/utils": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.8.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bun-types": ["bun-types@1.2.6", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-FbCKyr5KDiPULUzN/nm5oqQs9nXCHD8dVc64BArxJadCvbNzAI6lUWGh9fSJZWeDIRD38ikceBU8Kj/Uh+53oQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + + "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=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], + + "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.7.5", "", {}, "sha512-fDOK5W2C1vZACsgLONigdZTRZxuBqFtcKh7bUQ5cVSbwI2RWjloJDcgFOVzbQrlI6pCmhlTsVYZ7zpLj4m4qMQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-cron": ["node-cron@3.0.3", "", { "dependencies": { "uuid": "8.3.2" } }, "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + } +} diff --git a/src/config/env.ts b/src/config/env.ts index f41c59f..edabea4 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -1,46 +1,50 @@ -import { z } from 'zod'; +import { z } from "zod"; // Define environment schema with Zod const envSchema = z.object({ - // Server Configuration - PORT: z.string().default('8000'), - NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), - - // Twitter API Credentials - CLIENT_ID: z.string().min(1, 'Twitter Client ID is required'), - CLIENT_SECRET: z.string().min(1, 'Twitter Client Secret is required'), - - // Encryption Settings - ENCRYPTION_KEY: z.string().min(32, 'Encryption key should be at least 32 characters long'), - ENCRYPTION_SALT: z.string().min(1, 'Encryption salt is required'), - IV: z.string().min(1, 'Initialization vector is required'), - - // ChainGPT API - CHAINGPT_API_KEY: z.string().min(1, 'ChainGPT API key is required'), + // Server Configuration + PORT: z.string().default("8000"), + NODE_ENV: z + .enum(["development", "production", "test"]) + .default("development"), + + // Twitter API Credentials + TWITTER_CLIENT_ID: z.string().min(1, "Twitter Client ID is required"), + TWITTER_CLIENT_SECRET: z.string().min(1, "Twitter Client Secret is required"), + + // Encryption Settings + ENCRYPTION_KEY: z + .string() + .min(32, "Encryption key should be at least 32 characters long"), + ENCRYPTION_SALT: z.string().min(1, "Encryption salt is required"), + ENCRYPTION_IV: z.string().min(1, "Initialization vector is required"), + + // ChainGPT API + CHAINGPT_API_KEY: z.string().min(1, "ChainGPT API key is required"), }); // Parse and validate environment variables // Bun automatically loads .env file, no need for dotenv const parseEnv = () => { - try { - return envSchema.parse(process.env); - } catch (error) { - if (error instanceof z.ZodError) { - const errorMessages = error.errors.map(err => { - return `${err.path.join('.')}: ${err.message}`; - }); - - console.error('โŒ Invalid environment variables:'); - errorMessages.forEach(message => console.error(` - ${message}`)); - process.exit(1); - } - - throw error; + try { + return envSchema.parse(process.env); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors.map((err) => { + return `${err.path.join(".")}: ${err.message}`; + }); + + console.error("โŒ Invalid environment variables:"); + errorMessages.forEach((message) => console.error(` - ${message}`)); + process.exit(1); } + + throw error; + } }; // Export validated environment variables export const env = parseEnv(); // Type definition for environment variables -export type Env = z.infer; \ No newline at end of file +export type Env = z.infer; diff --git a/src/controllers/token.controller.ts b/src/controllers/token.controller.ts index 51ff19f..ed0ae5e 100644 --- a/src/controllers/token.controller.ts +++ b/src/controllers/token.controller.ts @@ -1,7 +1,7 @@ -import { Context } from 'hono'; -import { env } from '../config/env'; -import { saveTokens } from '../utils/encryption'; -import { ApiResponse, TokenLoadRequest } from '../types'; +import { Context } from "hono"; +import { env } from "../config/env"; +import { saveTokens } from "../utils/encryption"; +import { ApiResponse, TokenLoadRequest } from "../types"; /** * Load and encrypt Twitter tokens @@ -9,30 +9,36 @@ import { ApiResponse, TokenLoadRequest } from '../types'; * @returns Response with token save result */ export const loadTokens = async (c: Context): Promise => { - try { - const { accessToken, refreshToken } = await c.req.json(); + try { + const { accessToken, refreshToken } = await c.req.json(); - if (!accessToken || !refreshToken) { - return c.json({ - success: false, - message: 'Access token and refresh token are required', - error: 'Missing required fields: accessToken and/or refreshToken' - }, 400); - } + if (!accessToken || !refreshToken) { + return c.json( + { + success: false, + message: "Access token and refresh token are required", + error: "Missing required fields: accessToken and/or refreshToken", + }, + 400 + ); + } - await saveTokens(accessToken, refreshToken, env.ENCRYPTION_KEY); + await saveTokens(accessToken, refreshToken, env.ENCRYPTION_KEY); - return c.json({ - success: true, - message: 'Tokens saved successfully' - }); - } catch (error) { - console.error('Error saving tokens:', error); + return c.json({ + success: true, + message: "Tokens saved successfully", + }); + } catch (error) { + console.error("Error saving tokens:", error); - return c.json({ - success: false, - message: 'Failed to save tokens', - error: error instanceof Error ? error.message : 'Unknown error' - }, 500); - } -}; \ No newline at end of file + return c.json( + { + success: false, + message: "Failed to save tokens", + error: error instanceof Error ? error.message : "Unknown error", + }, + 500 + ); + } +}; diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts index 677f1fe..1a4b75a 100644 --- a/src/controllers/webhook.controller.ts +++ b/src/controllers/webhook.controller.ts @@ -1,11 +1,15 @@ -import { Context } from 'hono'; -import axios from 'axios'; -import { env } from '../config/env'; -import { uploadTwitterPostTweet } from '../services/twitter.service'; -import { ApiResponse, TweetWebhookRequest, WebhookRegistrationRequest } from '../types'; +import { Context } from "hono"; +import axios from "axios"; +import { env } from "../config/env"; +import { uploadTwitterPostTweet } from "../services/twitter.service"; +import { + ApiResponse, + TweetWebhookRequest, + WebhookRegistrationRequest, +} from "../types"; // ChainGPT API URL -const CHAINGPT_API_URL = 'https://webapi.chaingpt.org'; +const CHAINGPT_API_URL = "https://webapi.chaingpt.org"; /** * Register a webhook with ChainGPT @@ -13,41 +17,47 @@ const CHAINGPT_API_URL = 'https://webapi.chaingpt.org'; * @returns Response with registration result */ export const registerWebhook = async (c: Context): Promise => { - try { - const { url } = await c.req.json(); + try { + const { url } = await c.req.json(); - if (!url) { - return c.json({ - success: false, - message: 'URL is required', - error: 'Missing required field: url' - }, 400); - } + if (!url) { + return c.json( + { + success: false, + message: "URL is required", + error: "Missing required field: url", + }, + 400 + ); + } - const response = await axios.post( - `${CHAINGPT_API_URL}/webhook-subscription/register`, - { url }, - { - headers: { - 'api-key': env.CHAINGPT_API_KEY, - }, - } - ); + const response = await axios.post( + `${CHAINGPT_API_URL}/webhook-subscription/register`, + { url }, + { + headers: { + "api-key": env.CHAINGPT_API_KEY, + }, + } + ); - return c.json({ - success: true, - message: 'Webhook registered successfully', - data: response.data - }); - } catch (error) { - console.error('Error registering webhook:', error); + return c.json({ + success: true, + message: "Webhook registered successfully", + data: response.data, + }); + } catch (error) { + console.error("Error registering webhook:", error); - return c.json({ - success: false, - message: 'Failed to register webhook', - error: error instanceof Error ? error.message : 'Unknown error' - }, 500); - } + return c.json( + { + success: false, + message: "Failed to register webhook", + error: error instanceof Error ? error.message : "Unknown error", + }, + 500 + ); + } }; /** @@ -56,34 +66,40 @@ export const registerWebhook = async (c: Context): Promise => { * @returns Response with tweet result */ export const tweetWebhook = async (c: Context): Promise => { - try { - const { tweet } = await c.req.json(); + try { + const { tweet } = await c.req.json(); - if (!tweet) { - return c.json({ - success: false, - message: 'Tweet content is required', - error: 'Missing required field: tweet' - }, 400); - } + if (!tweet) { + return c.json( + { + success: false, + message: "Tweet content is required", + error: "Missing required field: tweet", + }, + 400 + ); + } - let tweetText = tweet; - // let tweetText = tweet.slice(0, 270); + let tweetText = tweet; + // let tweetText = tweet.slice(0, 270); - const response = await uploadTwitterPostTweet(tweetText); + const response = await uploadTwitterPostTweet(tweetText); - return c.json({ - success: true, - message: 'Tweet posted successfully', - data: { tweetText, response } - }); - } catch (error) { - console.error('Error posting tweet via webhook:', error); + return c.json({ + success: true, + message: "Tweet posted successfully", + data: { tweetText, response }, + }); + } catch (error) { + console.error("Error posting tweet via webhook:", error); - return c.json({ - success: false, - message: 'Failed to post tweet', - error: error instanceof Error ? error.message : 'Unknown error' - }, 500); - } -}; \ No newline at end of file + return c.json( + { + success: false, + message: "Failed to post tweet", + error: error instanceof Error ? error.message : "Unknown error", + }, + 500 + ); + } +}; diff --git a/src/index.ts b/src/index.ts index d75dbd2..47bce41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,38 +1,41 @@ -import { Hono } from 'hono'; -import { logger } from 'hono/logger'; -import { prettyJSON } from 'hono/pretty-json'; -import { env } from './config/env'; -import apiRouter from './routes'; -import { scheduleTweets } from './jobs/tweet.job'; +import { Hono } from "hono"; +import { logger } from "hono/logger"; +import { prettyJSON } from "hono/pretty-json"; +import { env } from "./config/env"; +import apiRouter from "./routes"; +import { scheduleTweets } from "./jobs/tweet.job"; // Create Hono app const app = new Hono(); // Middleware -app.use('*', logger()); -app.use('*', prettyJSON()); +app.use("*", logger()); +app.use("*", prettyJSON()); // Default route -app.get('/', (c) => { - return c.json({ - message: 'Hi, this is Twitter AI Agent developed by ChainGPT', - version: '1.0.0', - status: 'running' - }); +app.get("/", (c) => { + return c.json({ + message: "Hi, this is Twitter AI Agent developed by ChainGPT", + version: "1.0.0", + status: "running", + }); }); // API routes -app.route('/api', apiRouter); +app.route("/api", apiRouter); // Error handling middleware app.onError((err, c) => { - console.error('Global Error Handler:', err); - - return c.json({ - success: false, - message: 'Internal Server Error', - error: err.message - }, 500); + console.error("Global Error Handler:", err); + + return c.json( + { + success: false, + message: "Internal Server Error", + error: err.message, + }, + 500 + ); }); // Start the server with Bun @@ -42,16 +45,16 @@ console.log(`Starting server in ${env.NODE_ENV} mode...`); // Start the server using Bun Bun.serve({ - fetch: app.fetch, - port + fetch: app.fetch, + port, }); console.log(`๐Ÿš€ Twitter AI Agent listening on port ${port}`); // Start tweet scheduler try { - scheduleTweets(); - console.log('Tweet scheduler started successfully'); + scheduleTweets(); + console.log("Tweet scheduler started successfully"); } catch (error) { - console.error('Failed to start tweet scheduler:', error); -} \ No newline at end of file + console.error("Failed to start tweet scheduler:", error); +} diff --git a/src/routes/token.routes.ts b/src/routes/token.routes.ts index 21ed3b6..e88829b 100644 --- a/src/routes/token.routes.ts +++ b/src/routes/token.routes.ts @@ -1,10 +1,10 @@ -import { Hono } from 'hono'; -import { loadTokens } from '../controllers/token.controller'; +import { Hono } from "hono"; +import { loadTokens } from "../controllers/token.controller"; // Create a Hono router for token routes const tokenRouter = new Hono(); // Register token routes -tokenRouter.post('/', loadTokens); +tokenRouter.post("/", loadTokens); -export default tokenRouter; \ No newline at end of file +export default tokenRouter; diff --git a/src/services/twitter.service.ts b/src/services/twitter.service.ts index af81a7e..e598942 100644 --- a/src/services/twitter.service.ts +++ b/src/services/twitter.service.ts @@ -1,43 +1,49 @@ -import axios from 'axios'; -import { env } from '../config/env'; -import { loadTokens, saveTokens } from '../utils/encryption'; -import { TwitterOAuthResponse, TwitterPostResponse } from '../types'; +import axios from "axios"; +import { env } from "../config/env"; +import { loadTokens, saveTokens } from "../utils/encryption"; +import { TwitterOAuthResponse, TwitterPostResponse } from "../types"; // ChainGPT API URL -const CHAINGPT_API_URL = 'https://webapi.chaingpt.org'; +const CHAINGPT_API_URL = "https://webapi.chaingpt.org"; /** * Get Twitter access token by using an existing refresh token * @returns Promise containing the refreshed access token */ export const getAccessToken = async (): Promise => { + try { + const tokens = await loadTokens(env.ENCRYPTION_KEY); + + // Check if we need to refresh the token try { - const tokens = await loadTokens(env.ENCRYPTION_KEY); - - // Check if we need to refresh the token - try { - // Attempt to use the current token to see if it's still valid - await axios.get('https://api.twitter.com/2/users/me', { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }); - - // If no error is thrown, token is still valid - return tokens.accessToken; - } catch (error) { - // Token expired, refresh it - const newTokens = await refreshAccessToken(tokens.refreshToken); - - // Save new tokens - await saveTokens(newTokens.accessToken, newTokens.refreshToken, env.ENCRYPTION_KEY); - - return newTokens.accessToken; - } + // Attempt to use the current token to see if it's still valid + await axios.get("https://api.twitter.com/2/users/me", { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }); + + // If no error is thrown, token is still valid + return tokens.accessToken; } catch (error) { - console.error('Error getting Twitter access token:', error); - throw new Error('Failed to get Twitter access token. Check if tokens are set up correctly.'); + // Token expired, refresh it + const newTokens = await refreshAccessToken(tokens.refreshToken); + + // Save new tokens + await saveTokens( + newTokens.accessToken, + newTokens.refreshToken, + env.ENCRYPTION_KEY + ); + + return newTokens.accessToken; } + } catch (error) { + console.error("Error getting Twitter access token:", error); + throw new Error( + "Failed to get Twitter access token. Check if tokens are set up correctly." + ); + } }; /** @@ -45,38 +51,40 @@ export const getAccessToken = async (): Promise => { * @param refreshToken - The Twitter API refresh token * @returns Promise with updated access and refresh tokens */ -export const refreshAccessToken = async (refreshToken: string): Promise<{ - accessToken: string; - refreshToken: string; +export const refreshAccessToken = async ( + refreshToken: string +): Promise<{ + accessToken: string; + refreshToken: string; }> => { - try { - const params = new URLSearchParams(); - params.append('refresh_token', refreshToken); - params.append('grant_type', 'refresh_token'); - params.append('client_id', env.CLIENT_ID); - - const response = await axios.post( - 'https://api.twitter.com/2/oauth2/token', - params, - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - auth: { - username: env.CLIENT_ID, - password: env.CLIENT_SECRET, - }, - } - ); - - return { - accessToken: response.data.access_token, - refreshToken: response.data.refresh_token || refreshToken, - }; - } catch (error) { - console.error('Error refreshing access token:', error); - throw new Error('Failed to refresh Twitter access token'); - } + try { + const params = new URLSearchParams(); + params.append("refresh_token", refreshToken); + params.append("grant_type", "refresh_token"); + params.append("client_id", env.TWITTER_CLIENT_ID); + + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + params, + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + auth: { + username: env.TWITTER_CLIENT_ID, + password: env.TWITTER_CLIENT_SECRET, + }, + } + ); + + return { + accessToken: response.data.access_token, + refreshToken: response.data.refresh_token || refreshToken, + }; + } catch (error) { + console.error("Error refreshing access token:", error); + throw new Error("Failed to refresh Twitter access token"); + } }; /** @@ -85,26 +93,26 @@ export const refreshAccessToken = async (refreshToken: string): Promise<{ * @returns Promise with the generated tweet text */ export const getTextForTweet = async (prompt: string): Promise => { - try { - const response = await axios.post( - `${CHAINGPT_API_URL}/tweet-generator`, - { - prompt, - }, - { - headers: { - 'Content-Type': 'application/json', - 'api-key': env.CHAINGPT_API_KEY, - }, - } - ); - let tweetText = response.data.tweet - // let tweetText = response.data.tweet.slice(0, 270); // use this for base twitter api key - return tweetText; - } catch (error) { - console.error('Error generating tweet text:', error); - throw new Error('Failed to generate tweet content using ChainGPT API'); - } + try { + const response = await axios.post( + `${CHAINGPT_API_URL}/tweet-generator`, + { + prompt, + }, + { + headers: { + "Content-Type": "application/json", + "api-key": env.CHAINGPT_API_KEY, + }, + } + ); + let tweetText = response.data.tweet; + // let tweetText = response.data.tweet.slice(0, 270); // use this for base twitter api key + return tweetText; + } catch (error) { + console.error("Error generating tweet text:", error); + throw new Error("Failed to generate tweet content using ChainGPT API"); + } }; /** @@ -113,26 +121,28 @@ export const getTextForTweet = async (prompt: string): Promise => { * @param message - The message to tweet * @returns Promise with the Twitter API response */ -export const postTweet = async (accessToken: string, message: string): Promise => { - try { - const response = await axios.post( - 'https://api.twitter.com/2/tweets', - { - text: message, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - } - ); - - return response.data; - } catch (error) { - console.error('Error posting tweet:', error); - throw new Error('Failed to post tweet to Twitter'); - } +export const postTweet = async ( + accessToken: string, + message: string +): Promise => { + try { + const response = await axios.post( + "https://api.twitter.com/2/tweets", + { + text: message, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + } + ); + return response.data; + } catch (error) { + console.error("Error posting tweet:", error); + throw error; + } }; /** @@ -140,25 +150,27 @@ export const postTweet = async (accessToken: string, message: string): Promise => { - try { - const tweet = await getTextForTweet(prompt); - const accessToken = await getAccessToken(); - const response = await postTweet(accessToken, tweet); - - console.log(`Tweet posted successfully: ${tweet}`); - - return { - response, - tweet, - }; - } catch (error) { - console.error('Error generating and posting tweet:', error); - throw error; - } + try { + const tweet = await getTextForTweet(prompt); + const accessToken = await getAccessToken(); + const response = await postTweet(accessToken, tweet); + + console.log(`Tweet posted successfully: ${tweet}`); + + return { + response, + tweet, + }; + } catch (error) { + console.error("Error generating and posting tweet:", error); + throw error; + } }; /** @@ -166,16 +178,18 @@ export const generateAndPostTweet = async (prompt: string): Promise<{ * @param message - The message to tweet * @returns Promise with the Twitter API response */ -export const uploadTwitterPostTweet = async (message: string): Promise => { - try { - const accessToken = await getAccessToken(); - const response = await postTweet(accessToken, message); - - console.log(`Tweet posted successfully: ${message}`); - - return response; - } catch (error) { - console.error('Error posting tweet:', error); - throw error; - } -}; \ No newline at end of file +export const uploadTwitterPostTweet = async ( + message: string +): Promise => { + try { + const accessToken = await getAccessToken(); + const response = await postTweet(accessToken, message); + + console.log(`Tweet posted successfully: ${message}`); + + return response; + } catch (error) { + console.error("Error posting tweet:", error); + throw error; + } +}; diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index 3f1bb55..c0ed359 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -1,18 +1,18 @@ -import { join, dirname } from 'path'; -import { env } from '../config/env'; -import { EncryptedTokens, TwitterTokens } from '../types'; -import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; +import { join, dirname } from "path"; +import { env } from "../config/env"; +import { EncryptedTokens, TwitterTokens } from "../types"; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"; // File path for storing encrypted tokens -const TOKENS_FILE_PATH = join(import.meta.dir, '../../data/tokens.json'); +const TOKENS_FILE_PATH = join(import.meta.dir, "../../data/tokens.json"); // Encryption settings -const ALGORITHM = 'AES-GCM'; // Using Web Crypto API algorithm name +const ALGORITHM = "AES-GCM"; // Using Web Crypto API algorithm name const KEY_LENGTH = 32; // 256-bit key -// Get salt and IV from environment variables -const SALT = new Uint8Array(Buffer.from(env.ENCRYPTION_SALT, 'hex')); -const IV = new Uint8Array(Buffer.from(env.IV, 'hex')); +// Get salt and ENCRYPTION_IV from environment variables +const SALT = new Uint8Array(Buffer.from(env.ENCRYPTION_SALT, "hex")); +const ENCRYPTION_IV = new Uint8Array(Buffer.from(env.ENCRYPTION_IV, "hex")); /** * Convert a string to a key using PBKDF2 @@ -20,29 +20,29 @@ const IV = new Uint8Array(Buffer.from(env.IV, 'hex')); * @returns A Promise containing the derived key */ async function deriveKey(encryptionKey: string): Promise { - // Import the encryption key as raw material - const encoder = new TextEncoder(); - const keyMaterial = await crypto.subtle.importKey( - 'raw', - encoder.encode(encryptionKey), - 'PBKDF2', - false, - ['deriveKey'] - ); - - // Derive a key using PBKDF2 - return crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt: SALT, - iterations: 100000, - hash: 'SHA-256' - }, - keyMaterial, - { name: ALGORITHM, length: KEY_LENGTH * 8 }, - false, - ['encrypt', 'decrypt'] - ); + // Import the encryption key as raw material + const encoder = new TextEncoder(); + const keyMaterial = await crypto.subtle.importKey( + "raw", + encoder.encode(encryptionKey), + "PBKDF2", + false, + ["deriveKey"] + ); + + // Derive a key using PBKDF2 + return crypto.subtle.deriveKey( + { + name: "PBKDF2", + salt: SALT, + iterations: 100000, + hash: "SHA-256", + }, + keyMaterial, + { name: ALGORITHM, length: KEY_LENGTH * 8 }, + false, + ["encrypt", "decrypt"] + ); } /** @@ -51,23 +51,26 @@ async function deriveKey(encryptionKey: string): Promise { * @param encryptionKey - The encryption key * @returns The encrypted text with authentication tag */ -export async function encrypt(text: string, encryptionKey: string): Promise { - const key = await deriveKey(encryptionKey); - const encoder = new TextEncoder(); - const data = encoder.encode(text); - - // Encrypt the data - const encryptedBuffer = await crypto.subtle.encrypt( - { - name: ALGORITHM, - iv: IV - }, - key, - data - ); - - // Convert the encrypted buffer to base64 - return Buffer.from(encryptedBuffer).toString('base64'); +export async function encrypt( + text: string, + encryptionKey: string +): Promise { + const key = await deriveKey(encryptionKey); + const encoder = new TextEncoder(); + const data = encoder.encode(text); + + // Encrypt the data + const encryptedBuffer = await crypto.subtle.encrypt( + { + name: ALGORITHM, + iv: ENCRYPTION_IV, + }, + key, + data + ); + + // Convert the encrypted buffer to base64 + return Buffer.from(encryptedBuffer).toString("base64"); } /** @@ -76,23 +79,26 @@ export async function encrypt(text: string, encryptionKey: string): Promise { - const key = await deriveKey(encryptionKey); - const encryptedBuffer = Buffer.from(encryptedText, 'base64'); - - // Decrypt the data - const decryptedBuffer = await crypto.subtle.decrypt( - { - name: ALGORITHM, - iv: IV - }, - key, - encryptedBuffer - ); - - // Convert the decrypted buffer to string - const decoder = new TextDecoder(); - return decoder.decode(decryptedBuffer); +export async function decrypt( + encryptedText: string, + encryptionKey: string +): Promise { + const key = await deriveKey(encryptionKey); + const encryptedBuffer = Buffer.from(encryptedText, "base64"); + + // Decrypt the data + const decryptedBuffer = await crypto.subtle.decrypt( + { + name: ALGORITHM, + iv: ENCRYPTION_IV, + }, + key, + encryptedBuffer + ); + + // Convert the decrypted buffer to string + const decoder = new TextDecoder(); + return decoder.decode(decryptedBuffer); } /** @@ -102,25 +108,25 @@ export async function decrypt(encryptedText: string, encryptionKey: string): Pro * @param encryptionKey - The encryption key */ export async function saveTokens( - accessToken: string, - refreshToken: string, - encryptionKey: string + accessToken: string, + refreshToken: string, + encryptionKey: string ): Promise { - const encryptedAccessToken = await encrypt(accessToken, encryptionKey); - const encryptedRefreshToken = await encrypt(refreshToken, encryptionKey); + const encryptedAccessToken = await encrypt(accessToken, encryptionKey); + const encryptedRefreshToken = await encrypt(refreshToken, encryptionKey); - const tokens: EncryptedTokens = { - encryptedAccessToken, - encryptedRefreshToken - }; + const tokens: EncryptedTokens = { + encryptedAccessToken, + encryptedRefreshToken, + }; - // Ensure directory exists - const dirPath = join(dirname(TOKENS_FILE_PATH)); - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { recursive: true }); - } + // Ensure directory exists + const dirPath = join(dirname(TOKENS_FILE_PATH)); + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { recursive: true }); + } - writeFileSync(TOKENS_FILE_PATH, JSON.stringify(tokens, null, 2)); + writeFileSync(TOKENS_FILE_PATH, JSON.stringify(tokens, null, 2)); } /** @@ -128,16 +134,26 @@ export async function saveTokens( * @param encryptionKey - The encryption key * @returns The decrypted Twitter tokens */ -export async function loadTokens(encryptionKey: string): Promise { - if (!existsSync(TOKENS_FILE_PATH)) { - throw new Error('Twitter tokens file not found. Please set up tokens first.'); - } +export async function loadTokens( + encryptionKey: string +): Promise { + if (!existsSync(TOKENS_FILE_PATH)) { + throw new Error( + "Twitter tokens file not found. Please set up tokens first." + ); + } - const tokensData = readFileSync(TOKENS_FILE_PATH, 'utf8'); - const encryptedTokens: EncryptedTokens = JSON.parse(tokensData); + const tokensData = readFileSync(TOKENS_FILE_PATH, "utf8"); + const encryptedTokens: EncryptedTokens = JSON.parse(tokensData); - const accessToken = await decrypt(encryptedTokens.encryptedAccessToken, encryptionKey); - const refreshToken = await decrypt(encryptedTokens.encryptedRefreshToken, encryptionKey); + const accessToken = await decrypt( + encryptedTokens.encryptedAccessToken, + encryptionKey + ); + const refreshToken = await decrypt( + encryptedTokens.encryptedRefreshToken, + encryptionKey + ); - return { accessToken, refreshToken }; -} \ No newline at end of file + return { accessToken, refreshToken }; +} From 0700bd5d1193b339bf4c2ab5e70959c8e9fd97f7 Mon Sep 17 00:00:00 2001 From: sorooj Date: Tue, 15 Apr 2025 20:35:42 +0500 Subject: [PATCH 022/100] updated twitter token generation --- README.md | 176 +--------------------------- twitterTokenGeneration.md | 241 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 172 deletions(-) create mode 100644 twitterTokenGeneration.md diff --git a/README.md b/README.md index dd9bab6..fb3a3a9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ AgenticOS is a TypeScript-based AI agent that automates tweet generation and pub ## โš™๏ธ Requirements - [Bun Runtime](https://bun.sh) (v1.0 or newer) -- Twitter API credentials (OAuth 2.0) +- Twitter API credentials (OAuth 2.0) - for free accounts please refer to character limit per tweet - ChainGPT API Key ([Get one here](https://app.chaingpt.org/apidashboard)) - ChainGPT Credits ([Purchase credits](https://app.chaingpt.org/addcredits)) @@ -61,8 +61,8 @@ Update `.env` with your details: PORT=8000 NODE_ENV=development -TWITTER_CLIENT_ID=your_twitter_client_id -TWITTER_CLIENT_SECRET=your_twitter_client_secret +TWITTER_CLIENT_ID=your_twitter_client_id # generated from Twitter developer portal +TWITTER_CLIENT_SECRET=your_twitter_client_secret # generated from Twitter developer portal ENCRYPTION_KEY=your_32_character_encryption_key ENCRYPTION_SALT=your_hex_encryption_salt @@ -101,175 +101,7 @@ POST /api/tokens ### Generate access and refresh tokens -If you require quick code to generate twitter tokens then use below code - -```bash -# Imports -import express from "express"; -import axios from "axios"; -import crypto from "crypto"; -import querystring from "querystring"; -import session from "express-session"; -import { Buffer } from "buffer"; -``` - -after downloading and importing packages we need to make some necessary configurations - -```bash -# congigurations -type Request = express.Request; -type Response = express.Response; - -declare module "express-session" { - interface Session { - codeVerifier: string; - } -} - -interface TwitterTokens { - access_token: string; - refresh_token: string; -} - -# Configuration -const config = { - clientId: "your client id", - clientSecret: "your client secret", - redirectUri: "redirect url", - port: 8000, - sessionSecret: "session secret", -}; - -# Initialize Express app -const app = express(); - -# Session middleware -app.use( - session({ - secret: config.sessionSecret, - resave: false, - saveUninitialized: true, - }) -); - -``` - -Now we setup generatePKCE - -```bash -# Generate PKCE code verifier and challenge -const generatePKCE = (): { codeVerifier: string; codeChallenge: string } => { - const codeVerifier = crypto.randomBytes(32).toString("base64url"); - const codeChallenge = crypto - .createHash("sha256") - .update(codeVerifier) - .digest("base64url"); - return { codeVerifier, codeChallenge }; -}; -``` - -Following login endpoint can be used to login to twitter, after that login tokens will be generated in callcaback endpoint - -```bash -# Login route - initiates OAuth flow -app.get("/login", (req: Request, res: Response): void => { - const { codeVerifier, codeChallenge } = generatePKCE(); - const state = crypto.randomBytes(16).toString("hex"); - - # Store code verifier in session - req.session.codeVerifier = codeVerifier; - - const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify( - { - response_type: "code", - client_id: config.clientId, - redirect_uri: config.redirectUri, - scope: "tweet.read users.read tweet.write offline.access", - state, - code_challenge: codeChallenge, - code_challenge_method: "S256", - } - )}`; - - res.redirect(authorizationUrl); -}); -``` - -follwoing callback will be called after /login - -```bash -app.get("/callback", async (req: Request, res: Response): Promise => { - const code = req.query.code as string; - const codeVerifier = req.session.codeVerifier; - - if (!code || !codeVerifier) { - res.status(400).send("Authorization failed: Missing code or verifier"); - return; - } - - const basicAuth = Buffer.from( - `${config.clientId}:${config.clientSecret}` - ).toString("base64"); - - try { - const response = await axios.post( - "https://api.twitter.com/2/oauth2/token", - querystring.stringify({ - code, - client_id: config.clientId, - redirect_uri: config.redirectUri, - code_verifier: codeVerifier, - grant_type: "authorization_code", - }), - { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${basicAuth}`, - }, - } - ); - - const { access_token, refresh_token } = response.data; - console.log( - `Access and refresh tokens received: ${JSON.stringify( - { access_token, refresh_token }, - null, - 2 - )}` - ); - res.send( - `Access and refresh tokens received: ${JSON.stringify( - { access_token, refresh_token }, - null, - 2 - )}` - ); - } catch (error: any) { - if (axios.isAxiosError(error)) { - res - .status(500) - .send( - `Error during the token exchange: ${JSON.stringify( - error.response?.data || error.message - )}` - ); - } else { - res.status(500).send("An unexpected error occurred"); - } - } -}); -``` - -Starting server - -```bash -# Start the server -app.listen(config.port, () => { - console.log( - `Access and Refresh Token Generator listening on port ${config.port}` - ); -}); -``` +You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). ## ๐Ÿ“… Automated Tweeting Workflows diff --git a/twitterTokenGeneration.md b/twitterTokenGeneration.md new file mode 100644 index 0000000..328dfe1 --- /dev/null +++ b/twitterTokenGeneration.md @@ -0,0 +1,241 @@ +# ๐Ÿ”‘ Twitter Token Generation Guide + +This guide provides a complete solution for generating Twitter OAuth 2.0 access and refresh tokens using Express.js. + +## ๐Ÿ“‹ Prerequisites + +- Node.js (v14 or newer) +- npm or yarn +- Twitter Developer Account with OAuth 2.0 credentials + +## ๐Ÿ› ๏ธ Installation + +```bash +# Create a new directory +mkdir twitter-token-generator +cd twitter-token-generator + +# Initialize a new Node.js project +npm init -y + +# Install required dependencies +npm install express axios crypto querystring express-session +npm install --save-dev typescript ts-node @types/express @types/express-session + +# Create TypeScript configuration +npx tsc --init +``` + +## ๐Ÿ“ Code Implementation + +### ๐Ÿ”ง Imports and Type Definitions + +```typescript +// Required imports +import express from "express"; +import axios from "axios"; +import crypto from "crypto"; +import querystring from "querystring"; +import session from "express-session"; +import { Buffer } from "buffer"; + +// Type definitions +type Request = express.Request; +type Response = express.Response; + +declare module "express-session" { + interface Session { + codeVerifier: string; + } +} + +interface TwitterTokens { + access_token: string; + refresh_token: string; +} +``` + +### โš™๏ธ Configuration + +```typescript +// Configuration +const config = { + clientId: "your_twitter_client_id", + clientSecret: "your_twitter_client_secret", + redirectUri: "http://localhost:8000/callback", + port: 8000, + sessionSecret: "your_session_secret", +}; + +// Initialize Express app +const app = express(); + +// Session middleware +app.use( + session({ + secret: config.sessionSecret, + resave: false, + saveUninitialized: true, + }) +); +``` + +### ๐Ÿ” PKCE Generation + +```typescript +// Generate PKCE code verifier and challenge +const generatePKCE = (): { codeVerifier: string; codeChallenge: string } => { + const codeVerifier = crypto.randomBytes(32).toString("base64url"); + const codeChallenge = crypto + .createHash("sha256") + .update(codeVerifier) + .digest("base64url"); + return { codeVerifier, codeChallenge }; +}; +``` + +### ๐Ÿ”— OAuth Endpoints + +#### Login Route + +```typescript +// Login route - initiates OAuth flow +app.get("/login", (req: Request, res: Response): void => { + const { codeVerifier, codeChallenge } = generatePKCE(); + const state = crypto.randomBytes(16).toString("hex"); + + // Store code verifier in session + req.session.codeVerifier = codeVerifier; + + const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify( + { + response_type: "code", + client_id: config.clientId, + redirect_uri: config.redirectUri, + scope: "tweet.read users.read tweet.write offline.access", + state, + code_challenge: codeChallenge, + code_challenge_method: "S256", + } + )}`; + + res.redirect(authorizationUrl); +}); +``` + +#### Callback Route + +```typescript +// Callback route - handles OAuth response +app.get("/callback", async (req: Request, res: Response): Promise => { + const code = req.query.code as string; + const codeVerifier = req.session.codeVerifier; + + if (!code || !codeVerifier) { + res.status(400).send("Authorization failed: Missing code or verifier"); + return; + } + + const basicAuth = Buffer.from( + `${config.clientId}:${config.clientSecret}` + ).toString("base64"); + + try { + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + querystring.stringify({ + code, + client_id: config.clientId, + client_secret: config.clientSecret, + redirect_uri: config.redirectUri, + code_verifier: codeVerifier, + grant_type: "authorization_code", + }), + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${basicAuth}`, + }, + } + ); + + const { access_token, refresh_token } = response.data; + console.log( + `Access and refresh tokens received: ${JSON.stringify( + { access_token, refresh_token }, + null, + 2 + )}` + ); + res.send( + `Access and refresh tokens received: ${JSON.stringify( + { access_token, refresh_token }, + null, + 2 + )}` + ); + } catch (error: any) { + if (axios.isAxiosError(error)) { + res + .status(500) + .send( + `Error during the token exchange: ${JSON.stringify( + error.response?.data || error.message + )}` + ); + } else { + res.status(500).send("An unexpected error occurred"); + } + } +}); +``` + +### ๐Ÿš€ Server Startup + +```typescript +// Start the server +app.listen(config.port, () => { + console.log( + `Access and Refresh Token Generator listening on port ${config.port}` + ); +}); +``` + +## ๐Ÿ“‹ Complete File + +Save all the code above to a file named `twitter-token-generator.ts`. + +## ๐Ÿš€ Running the Application + +```bash +# Run the application +node --loader ts-node/esm twitter-token-generator.ts +``` + +## ๐Ÿ”„ Usage Flow + +1. Visit `http://localhost:8000/login` in your browser +2. You'll be redirected to Twitter's authorization page +3. Log in and authorize the application +4. You'll be redirected back to your application +5. The access and refresh tokens will be displayed on the page + +## ๐Ÿ” Security Considerations + +- Store your client ID and client secret securely +- Use HTTPS in production +- Implement proper session management +- Consider using a more robust storage solution for the code verifier in production + +## ๐Ÿ”— Integration with AgenticOS + +After obtaining your tokens, you can add them to your AgenticOS application using the following endpoint: + +```bash +POST /api/tokens + +Body: { + "accessToken": "your_access_token", + "refreshToken": "your_refresh_token" +} +``` From 8831c8fa5062e6483c5011c85c5b4d29bc672713 Mon Sep 17 00:00:00 2001 From: sorooj Date: Wed, 16 Apr 2025 14:28:57 +0500 Subject: [PATCH 023/100] updated docs --- README.md | 20 ++++++++++---------- twitterTokenGeneration.md | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index fb3a3a9..e9e744d 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,9 @@ NODE_ENV=development TWITTER_CLIENT_ID=your_twitter_client_id # generated from Twitter developer portal TWITTER_CLIENT_SECRET=your_twitter_client_secret # generated from Twitter developer portal -ENCRYPTION_KEY=your_32_character_encryption_key -ENCRYPTION_SALT=your_hex_encryption_salt -ENCRYPTION_IV=your_hex_initialization_vector +ENCRYPTION_KEY=your_32_character_encryption_key # set a value and keep it secure +ENCRYPTION_SALT=your_hex_encryption_salt # set a value and keep it secure +ENCRYPTION_IV=your_hex_initialization_vector # set a value and keep it secure CHAINGPT_API_KEY=your_chaingpt_api_key ``` @@ -86,11 +86,15 @@ bun start ## Provide Twitter Access and Refresh Tokens +### Generate access and refresh tokens + +You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). + ### Add tokens to app ```bash # Add Twitter tokens to the application -POST /api/tokens +POST /api/tokens # Request body { @@ -99,10 +103,6 @@ POST /api/tokens } ``` -### Generate access and refresh tokens - -You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). - ## ๐Ÿ“… Automated Tweeting Workflows ### Workflow 1: Scheduled Tweeting (Cron) @@ -141,9 +141,9 @@ Body: { "categoryIds": [2, 3] } Register your webhook to automatically post updates: ```bash -POST {your_project_url}/api/webhook/register +POST {https://your-domain.com}/api/webhook/register -Body: { "url": "{your_project_url}/api/webhook/" } +Body: { "url": "{https://your-domain.com}/api/webhook/" } ``` AgenticOS will automatically post tweets from ChainGPT news updates. diff --git a/twitterTokenGeneration.md b/twitterTokenGeneration.md index 328dfe1..9814a15 100644 --- a/twitterTokenGeneration.md +++ b/twitterTokenGeneration.md @@ -60,11 +60,11 @@ interface TwitterTokens { ```typescript // Configuration const config = { - clientId: "your_twitter_client_id", - clientSecret: "your_twitter_client_secret", - redirectUri: "http://localhost:8000/callback", + clientId: "your_twitter_client_id", //twitter client id + clientSecret: "your_twitter_client_secret", // twitter secret + redirectUri: "http://localhost:8000/callback", //change prot or domain according to your configurations port: 8000, - sessionSecret: "your_session_secret", + sessionSecret: "your_session_secret", //set a value and keep it secure }; // Initialize Express app From 03393c7d752d3ad2b6285330bb723fda4318362e Mon Sep 17 00:00:00 2001 From: sorooj Date: Wed, 16 Apr 2025 15:16:48 +0500 Subject: [PATCH 024/100] readme update --- twitterTokenGeneration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twitterTokenGeneration.md b/twitterTokenGeneration.md index 9814a15..fbbb784 100644 --- a/twitterTokenGeneration.md +++ b/twitterTokenGeneration.md @@ -62,7 +62,7 @@ interface TwitterTokens { const config = { clientId: "your_twitter_client_id", //twitter client id clientSecret: "your_twitter_client_secret", // twitter secret - redirectUri: "http://localhost:8000/callback", //change prot or domain according to your configurations + redirectUri: "http://localhost:8000/callback", //change port or domain according to your configurations port: 8000, sessionSecret: "your_session_secret", //set a value and keep it secure }; From 79c89c68b4f1201519f6d4767e175c3ae1faf961 Mon Sep 17 00:00:00 2001 From: Maryam Naveed Date: Wed, 16 Apr 2025 15:47:08 +0500 Subject: [PATCH 025/100] AgenticOS for AI-driven Web3 insights and automation on X --- .env.example | 26 ++ .gitignore | 34 +++ .npmrc | 2 + README.md | 217 +++++++++++++++- bun.lock | 342 ++++++++++++++++++++++++++ data/schedule.json | 73 ++++++ package.json | 32 +++ src/config/env.ts | 50 ++++ src/controllers/token.controller.ts | 44 ++++ src/controllers/webhook.controller.ts | 105 ++++++++ src/index.ts | 60 +++++ src/jobs/tweet.job.ts | 79 ++++++ src/routes/index.ts | 12 + src/routes/token.routes.ts | 10 + src/routes/webhook.routes.ts | 11 + src/services/twitter.service.ts | 195 +++++++++++++++ src/types/index.ts | 88 +++++++ src/utils/encryption.ts | 159 ++++++++++++ tsconfig.json | 10 + twitterTokenGeneration.md | 241 ++++++++++++++++++ 20 files changed, 1789 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 bun.lock create mode 100644 data/schedule.json create mode 100644 package.json create mode 100644 src/config/env.ts create mode 100644 src/controllers/token.controller.ts create mode 100644 src/controllers/webhook.controller.ts create mode 100644 src/index.ts create mode 100644 src/jobs/tweet.job.ts create mode 100644 src/routes/index.ts create mode 100644 src/routes/token.routes.ts create mode 100644 src/routes/webhook.routes.ts create mode 100644 src/services/twitter.service.ts create mode 100644 src/types/index.ts create mode 100644 src/utils/encryption.ts create mode 100644 tsconfig.json create mode 100644 twitterTokenGeneration.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3fb4cb4 --- /dev/null +++ b/.env.example @@ -0,0 +1,26 @@ +# ============================= +# ๐Ÿš€ Server Configuration +# ============================= +PORT=8000 +NODE_ENV=development # 'production' or 'development' + +# ============================= +# ๐Ÿ”‘ Twitter API Credentials (OAuth 2.0) +# Obtain these from Twitter Developer Portal +# ============================= +TWITTER_CLIENT_ID=your_twitter_client_id +TWITTER_CLIENT_SECRET=your_twitter_client_secret + +# ============================= +# ๐Ÿ” Encryption Settings +# (Use secure, randomly-generated values) +# ============================= +ENCRYPTION_KEY=your_32_characters_encryption_key +ENCRYPTION_SALT=your_hex_encoded_salt +ENCRYPTION_IV=your_hex_encoded_initialization_vector + +# ============================= +# ๐Ÿค– ChainGPT API Configuration +# Obtain your API key from: https://app.chaingpt.org/apidashboard +# ============================= +CHAINGPT_API_KEY=your_chaingpt_api_key diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b0e802 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +#node_modules +node_modules/ +#env +.env + +# Bun files +node_modules/ +.env +bun.lockb + +# Build output +dist/ + +# Data files with sensitive information +data/tokens.json + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# OS files +.DS_Store +Thumbs.db diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f68dfcc --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +use-bun=true \ No newline at end of file diff --git a/README.md b/README.md index 4809b64..e9e744d 100644 --- a/README.md +++ b/README.md @@ -1 +1,216 @@ -# Twitter_AI_Agent \ No newline at end of file +# AgenticOS โ€“ Your AI Agent for Web3 on X (Twitter) + +**Built by ChainGPT** + +AgenticOS lets you effortlessly create and deploy your own intelligent AI agent on X (formerly Twitter)โ€”purpose-built for the Web3 ecosystem. Automate tasks like real-time market research, breaking crypto news, token analysis, and community engagement, enhancing your digital presence with 24/7 AI-driven insights. + +๐Ÿ“Œ **Live Demo**: [ChainGPT AI on X](https://x.com/ChainGPTAI) + +--- + +## ๐Ÿš€ Overview + +AgenticOS is a TypeScript-based AI agent that automates tweet generation and publishing, leveraging ChainGPT's advanced Web3 LLM API and the ultra-fast Bun runtime. Built for ease of integration and secure performance. + +### ๐Ÿ”ฅ Key Features + +- **AI-powered Tweet Generation** using ChainGPT's Web3 LLM +- **Scheduled Automated Tweets** via configurable Cron jobs +- **Webhook Integration** with ChainGPT for automatic real-time updates +- **Secure Token Storage** with encryption +- **Automatic Twitter Token Refresh** (OAuth 2.0) +- **TypeScript** for enhanced developer experience and type safety +- **Ultra-fast Bun Runtime** for optimal performance + +--- + +## โš™๏ธ Requirements + +- [Bun Runtime](https://bun.sh) (v1.0 or newer) +- Twitter API credentials (OAuth 2.0) - for free accounts please refer to character limit per tweet +- ChainGPT API Key ([Get one here](https://app.chaingpt.org/apidashboard)) +- ChainGPT Credits ([Purchase credits](https://app.chaingpt.org/addcredits)) + +Each generated tweet consumes 1 ChainGPT credit. + +--- + +## ๐Ÿ”‘ Quick Start + +### Step 1: Clone and Set Up + +```bash +git clone https://github.com/ChainGPT-org/AgenticOS.git +cd AgenticOS + +# Install Bun runtime +curl -fsSL https://bun.sh/install | bash + +# Install project dependencies +bun install + +# Configure your environment +cp .env.example .env +``` + +### Step 2: Configure `.env` + +Update `.env` with your details: + +```bash +PORT=8000 +NODE_ENV=development + +TWITTER_CLIENT_ID=your_twitter_client_id # generated from Twitter developer portal +TWITTER_CLIENT_SECRET=your_twitter_client_secret # generated from Twitter developer portal + +ENCRYPTION_KEY=your_32_character_encryption_key # set a value and keep it secure +ENCRYPTION_SALT=your_hex_encryption_salt # set a value and keep it secure +ENCRYPTION_IV=your_hex_initialization_vector # set a value and keep it secure + +CHAINGPT_API_KEY=your_chaingpt_api_key +``` + +--- + +## ๐Ÿšฉ Usage + +### Production Mode + +```bash +bun build +bun start +``` + +--- + +## Provide Twitter Access and Refresh Tokens + +### Generate access and refresh tokens + +You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). + +### Add tokens to app + +```bash +# Add Twitter tokens to the application +POST /api/tokens + +# Request body +{ + "accessToken": "your_access_token", + "refreshToken": "your_refresh_token" +} +``` + +## ๐Ÿ“… Automated Tweeting Workflows + +### Workflow 1: Scheduled Tweeting (Cron) + +Define your schedule in `data/schedule.json`: + +```json +{ + "14:30": "The future of AI in Web3", + "18:00": "Crypto markets update" +} +``` + +Tweets are auto-generated and posted according to this schedule (UTC). + +### Workflow 2: ChainGPT Webhook for Live News + +**Subscribe to Categories:** + +- Get available categories: + +```bash +GET https://webapi.chaingpt.org/category-subscription/ +``` + +- Subscribe to categories: + +```bash +POST https://webapi.chaingpt.org/category-subscription/subscribe + +Body: { "categoryIds": [2, 3] } +``` + +**Register Webhook:** + +Register your webhook to automatically post updates: + +```bash +POST {https://your-domain.com}/api/webhook/register + +Body: { "url": "{https://your-domain.com}/api/webhook/" } +``` + +AgenticOS will automatically post tweets from ChainGPT news updates. + +--- + +## ๐Ÿ“š Project Structure + +``` +twitter-ai-agent/ +โ”œโ”€โ”€ data/ +โ”‚ โ””โ”€โ”€ schedule.json +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ”œโ”€โ”€ controllers/ +โ”‚ โ”œโ”€โ”€ jobs/ +โ”‚ โ”œโ”€โ”€ routes/ +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”œโ”€โ”€ types/ +โ”‚ โ”œโ”€โ”€ utils/ +โ”‚ โ””โ”€โ”€ index.ts +โ”œโ”€โ”€ .env.example +โ”œโ”€โ”€ package.json +โ””โ”€โ”€ tsconfig.json +``` + +--- + +## ๐ŸŒ Why Choose Bun? + +- ๐Ÿš€ **Superior Performance**: Faster execution & startup +- ๐Ÿ›  **Built-in TypeScript & ESM Support** +- ๐ŸŽฏ **Simplified Development**: Integrated tools for testing & bundling +- ๐Ÿ“ฆ **Compatible with npm packages** + +--- + +## ๐Ÿ” Security + +- Secure encryption of Twitter tokens +- Environment variable validation +- Robust error handling + +--- + +## ๐Ÿค Contributing + +Contributions are welcome! Follow these steps: + +1. Fork this repository. +2. Create a branch: `git checkout -b feature/my-new-feature` +3. Commit changes: `git commit -am 'Add feature'` +4. Push changes: `git push origin feature/my-new-feature` +5. Open a Pull Request. + +--- + +## ๐Ÿ“œ License + +**ISC** + +## ๐Ÿง‘โ€๐Ÿ’ป Author + +**ChainGPT** + +## ๐Ÿ“ง Support + +Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-agent/issues). + +๐Ÿš€ **Happy Coding!** diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..58b3b6b --- /dev/null +++ b/bun.lock @@ -0,0 +1,342 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "twitter-ai-agent", + "dependencies": { + "axios": "^1.7.9", + "hono": "^4.1.5", + "node-cron": "^3.0.3", + "zod": "^3.22.4", + }, + "devDependencies": { + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "bun-types": "^1.2.4", + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "typescript": "^5.4.2", + }, + }, + }, + "packages": { + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.5.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], + + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="], + + "@types/node-cron": ["@types/node-cron@3.0.11", "", {}, "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="], + + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.18.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/type-utils": "7.18.0", "@typescript-eslint/utils": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.8.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bun-types": ["bun-types@1.2.6", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-FbCKyr5KDiPULUzN/nm5oqQs9nXCHD8dVc64BArxJadCvbNzAI6lUWGh9fSJZWeDIRD38ikceBU8Kj/Uh+53oQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + + "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=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], + + "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.7.5", "", {}, "sha512-fDOK5W2C1vZACsgLONigdZTRZxuBqFtcKh7bUQ5cVSbwI2RWjloJDcgFOVzbQrlI6pCmhlTsVYZ7zpLj4m4qMQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-cron": ["node-cron@3.0.3", "", { "dependencies": { "uuid": "8.3.2" } }, "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + } +} diff --git a/data/schedule.json b/data/schedule.json new file mode 100644 index 0000000..0428803 --- /dev/null +++ b/data/schedule.json @@ -0,0 +1,73 @@ +{ + "config": { + "persona": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart", + "maxLength": 280, + "timezone": "UTC" + }, + "schedule": { + "00:00": { + "type": "poll", + "instruction": "{{persona}} and good at analyzing the crypto market. Create fun, engaging polls for your audience to spark conversation. Provide 1 poll. It will be a poll on Twitter, so please provide it in the correct template. Only provide this information: Title / Question To Ask Option #1 Option #2 Option #3 Option #4" + }, + "00:30": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." + }, + "03:00": { + "type": "education", + "instruction": "{{persona}} and good at explaining complex crypto concepts and AI concepts in simple terms. Create a short, engaging tweet explaining one key blockchain or crypto concept in less than {{maxLength}} characters." + }, + "04:30": { + "type": "market_recap", + "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." + }, + "06:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:30": { + "type": "market_insight", + "instruction": "{{persona}} and good at market analysis. Write something about the market based on the fear & greed index and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters." + }, + "09:00": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." + }, + "10:30": { + "type": "nft", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the most hyped NFT collections and their volumes." + }, + "12:00": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "13:30": { + "type": "market_analysis", + "instruction": "{{persona}} and excellent at market analysis. Create a crypto market analysis report showing all the crypto related indicators that could be helpful. Start the report with: Here's a Comprehensive Analysis Report on Key Crypto Market Indicators: (Expand the tweet to view it in full ๐Ÿ‘‡)" + }, + "15:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "16:30": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." + }, + "18:00": { + "type": "trending_tokens", + "instruction": "{{persona}} and good at market research. Name 10 tokens that are trending right now. Write about it in this form: Hey fam, here are the top ten tokens trending right now ๐Ÿ‘‡ DYOR: $NAME $NAME $NAME $NAME. Top trending tokens on coinmarketcap" + }, + "19:30": { + "type": "news", + "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet about the latest and most important crypto news." + }, + "21:00": { + "type": "market_shift", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) announcing a major crypto market change. Based on all the information, market indicators, and news you have. Post about the most important or interesting market shift or event that is happening right now." + }, + "22:30": { + "type": "market_insight", + "instruction": "{{persona}} and good at market analysis. Write something about the market based on the bitcoin vs alts vs stablecoin dominance and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters, tweet style." + } + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..e39a20c --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "twitter-ai-agent", + "version": "1.0.0", + "main": "src/index.ts", + "type": "module", + "scripts": { + "dev": "bun --watch src/index.ts", + "build": "bun build ./src/index.ts --outdir ./dist --target node", + "start": "bun src/index.ts", + "lint": "eslint 'src/**/*.ts'", + "format": "prettier --write 'src/**/*.ts'", + "test": "bun test" + }, + "author": "ChainGPT", + "license": "ISC", + "description": "", + "dependencies": { + "axios": "^1.7.9", + "hono": "^4.1.5", + "node-cron": "^3.0.3", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "bun-types": "^1.2.4", + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "typescript": "^5.4.2" + } +} diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..edabea4 --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,50 @@ +import { z } from "zod"; + +// Define environment schema with Zod +const envSchema = z.object({ + // Server Configuration + PORT: z.string().default("8000"), + NODE_ENV: z + .enum(["development", "production", "test"]) + .default("development"), + + // Twitter API Credentials + TWITTER_CLIENT_ID: z.string().min(1, "Twitter Client ID is required"), + TWITTER_CLIENT_SECRET: z.string().min(1, "Twitter Client Secret is required"), + + // Encryption Settings + ENCRYPTION_KEY: z + .string() + .min(32, "Encryption key should be at least 32 characters long"), + ENCRYPTION_SALT: z.string().min(1, "Encryption salt is required"), + ENCRYPTION_IV: z.string().min(1, "Initialization vector is required"), + + // ChainGPT API + CHAINGPT_API_KEY: z.string().min(1, "ChainGPT API key is required"), +}); + +// Parse and validate environment variables +// Bun automatically loads .env file, no need for dotenv +const parseEnv = () => { + try { + return envSchema.parse(process.env); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors.map((err) => { + return `${err.path.join(".")}: ${err.message}`; + }); + + console.error("โŒ Invalid environment variables:"); + errorMessages.forEach((message) => console.error(` - ${message}`)); + process.exit(1); + } + + throw error; + } +}; + +// Export validated environment variables +export const env = parseEnv(); + +// Type definition for environment variables +export type Env = z.infer; diff --git a/src/controllers/token.controller.ts b/src/controllers/token.controller.ts new file mode 100644 index 0000000..ed0ae5e --- /dev/null +++ b/src/controllers/token.controller.ts @@ -0,0 +1,44 @@ +import { Context } from "hono"; +import { env } from "../config/env"; +import { saveTokens } from "../utils/encryption"; +import { ApiResponse, TokenLoadRequest } from "../types"; + +/** + * Load and encrypt Twitter tokens + * @param c - Hono context + * @returns Response with token save result + */ +export const loadTokens = async (c: Context): Promise => { + try { + const { accessToken, refreshToken } = await c.req.json(); + + if (!accessToken || !refreshToken) { + return c.json( + { + success: false, + message: "Access token and refresh token are required", + error: "Missing required fields: accessToken and/or refreshToken", + }, + 400 + ); + } + + await saveTokens(accessToken, refreshToken, env.ENCRYPTION_KEY); + + return c.json({ + success: true, + message: "Tokens saved successfully", + }); + } catch (error) { + console.error("Error saving tokens:", error); + + return c.json( + { + success: false, + message: "Failed to save tokens", + error: error instanceof Error ? error.message : "Unknown error", + }, + 500 + ); + } +}; diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts new file mode 100644 index 0000000..1a4b75a --- /dev/null +++ b/src/controllers/webhook.controller.ts @@ -0,0 +1,105 @@ +import { Context } from "hono"; +import axios from "axios"; +import { env } from "../config/env"; +import { uploadTwitterPostTweet } from "../services/twitter.service"; +import { + ApiResponse, + TweetWebhookRequest, + WebhookRegistrationRequest, +} from "../types"; + +// ChainGPT API URL +const CHAINGPT_API_URL = "https://webapi.chaingpt.org"; + +/** + * Register a webhook with ChainGPT + * @param c - Hono context + * @returns Response with registration result + */ +export const registerWebhook = async (c: Context): Promise => { + try { + const { url } = await c.req.json(); + + if (!url) { + return c.json( + { + success: false, + message: "URL is required", + error: "Missing required field: url", + }, + 400 + ); + } + + const response = await axios.post( + `${CHAINGPT_API_URL}/webhook-subscription/register`, + { url }, + { + headers: { + "api-key": env.CHAINGPT_API_KEY, + }, + } + ); + + return c.json({ + success: true, + message: "Webhook registered successfully", + data: response.data, + }); + } catch (error) { + console.error("Error registering webhook:", error); + + return c.json( + { + success: false, + message: "Failed to register webhook", + error: error instanceof Error ? error.message : "Unknown error", + }, + 500 + ); + } +}; + +/** + * Handle incoming webhook requests for posting tweets + * @param c - Hono context + * @returns Response with tweet result + */ +export const tweetWebhook = async (c: Context): Promise => { + try { + const { tweet } = await c.req.json(); + + if (!tweet) { + return c.json( + { + success: false, + message: "Tweet content is required", + error: "Missing required field: tweet", + }, + 400 + ); + } + + let tweetText = tweet; + // let tweetText = tweet.slice(0, 270); + + const response = await uploadTwitterPostTweet(tweetText); + + return c.json({ + success: true, + message: "Tweet posted successfully", + data: { tweetText, response }, + }); + } catch (error) { + console.error("Error posting tweet via webhook:", error); + + return c.json( + { + success: false, + message: "Failed to post tweet", + error: error instanceof Error ? error.message : "Unknown error", + }, + 500 + ); + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..47bce41 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,60 @@ +import { Hono } from "hono"; +import { logger } from "hono/logger"; +import { prettyJSON } from "hono/pretty-json"; +import { env } from "./config/env"; +import apiRouter from "./routes"; +import { scheduleTweets } from "./jobs/tweet.job"; + +// Create Hono app +const app = new Hono(); + +// Middleware +app.use("*", logger()); +app.use("*", prettyJSON()); + +// Default route +app.get("/", (c) => { + return c.json({ + message: "Hi, this is Twitter AI Agent developed by ChainGPT", + version: "1.0.0", + status: "running", + }); +}); + +// API routes +app.route("/api", apiRouter); + +// Error handling middleware +app.onError((err, c) => { + console.error("Global Error Handler:", err); + + return c.json( + { + success: false, + message: "Internal Server Error", + error: err.message, + }, + 500 + ); +}); + +// Start the server with Bun +const port = parseInt(env.PORT); + +console.log(`Starting server in ${env.NODE_ENV} mode...`); + +// Start the server using Bun +Bun.serve({ + fetch: app.fetch, + port, +}); + +console.log(`๐Ÿš€ Twitter AI Agent listening on port ${port}`); + +// Start tweet scheduler +try { + scheduleTweets(); + console.log("Tweet scheduler started successfully"); +} catch (error) { + console.error("Failed to start tweet scheduler:", error); +} diff --git a/src/jobs/tweet.job.ts b/src/jobs/tweet.job.ts new file mode 100644 index 0000000..8a4dcaa --- /dev/null +++ b/src/jobs/tweet.job.ts @@ -0,0 +1,79 @@ +import { schedule } from 'node-cron'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { generateAndPostTweet } from '../services/twitter.service'; + +// Path to the schedule configuration file +const CONFIG_PATH = join(import.meta.dir, '../../data/schedule.json'); + +/** + * Load schedule configuration from JSON file + * @returns The schedule configuration with config and schedule entries + */ +function loadConfig(): { config: any, schedule: Record } { + try { + const data = readFileSync(CONFIG_PATH, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading schedule config:', error); + return { config: {}, schedule: {} }; + } +} + +/** + * Process template strings in the instruction + * @param instruction - The instruction template + * @param config - The configuration with replacement values + * @returns The processed instruction + */ +function processTemplate(instruction: string, config: any): string { + return instruction.replace(/\{\{(\w+)\}\}/g, (match, key) => { + return config[key] || match; + }); +} + +/** + * Schedule tweets based on configuration + * Sets up cron jobs for each time entry in the schedule + */ +export function scheduleTweets(): void { + const { config, schedule: scheduleEntries } = loadConfig(); + + if (!scheduleEntries || Object.keys(scheduleEntries).length === 0) { + console.warn('No scheduled tweets found in configuration'); + return; + } + + console.log(`Setting up ${Object.keys(scheduleEntries).length} scheduled tweets`); + + for (const time in scheduleEntries) { + const entry = scheduleEntries[time]; + const { type, instruction } = entry; + + // Process the template to replace placeholders with actual values + const processedInstruction = processTemplate(instruction, config); + + const [hour, minute] = time.split(':'); + const timezone = config.timezone || 'UTC'; + + // Schedule the cron job + schedule( + `${minute} ${hour} * * *`, + async () => { + try { + console.log(`Running scheduled tweet for ${timezone} time: ${time} (Type: ${type})`); + await generateAndPostTweet(processedInstruction); + } catch (error) { + console.error(`Error executing scheduled tweet for time ${time}:`, error); + } + }, + { + timezone, + } + ); + + console.log(`Scheduled ${type} tweet for ${time} ${timezone}`); + } + + console.log('Tweet scheduler started successfully'); +} \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..4fa71e1 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,12 @@ +import { Hono } from 'hono'; +import webhookRouter from './webhook.routes'; +import tokenRouter from './token.routes'; + +// Create a Hono router for API routes +const apiRouter = new Hono(); + +// Register API routes +apiRouter.route('/webhook', webhookRouter); +apiRouter.route('/tokens', tokenRouter); + +export default apiRouter; \ No newline at end of file diff --git a/src/routes/token.routes.ts b/src/routes/token.routes.ts new file mode 100644 index 0000000..e88829b --- /dev/null +++ b/src/routes/token.routes.ts @@ -0,0 +1,10 @@ +import { Hono } from "hono"; +import { loadTokens } from "../controllers/token.controller"; + +// Create a Hono router for token routes +const tokenRouter = new Hono(); + +// Register token routes +tokenRouter.post("/", loadTokens); + +export default tokenRouter; diff --git a/src/routes/webhook.routes.ts b/src/routes/webhook.routes.ts new file mode 100644 index 0000000..cc8df83 --- /dev/null +++ b/src/routes/webhook.routes.ts @@ -0,0 +1,11 @@ +import { Hono } from 'hono'; +import { registerWebhook, tweetWebhook } from '../controllers/webhook.controller'; + +// Create a Hono router for webhook routes +const webhookRouter = new Hono(); + +// Register webhook routes +webhookRouter.post('/register', registerWebhook); +webhookRouter.post('/', tweetWebhook); + +export default webhookRouter; \ No newline at end of file diff --git a/src/services/twitter.service.ts b/src/services/twitter.service.ts new file mode 100644 index 0000000..e598942 --- /dev/null +++ b/src/services/twitter.service.ts @@ -0,0 +1,195 @@ +import axios from "axios"; +import { env } from "../config/env"; +import { loadTokens, saveTokens } from "../utils/encryption"; +import { TwitterOAuthResponse, TwitterPostResponse } from "../types"; + +// ChainGPT API URL +const CHAINGPT_API_URL = "https://webapi.chaingpt.org"; + +/** + * Get Twitter access token by using an existing refresh token + * @returns Promise containing the refreshed access token + */ +export const getAccessToken = async (): Promise => { + try { + const tokens = await loadTokens(env.ENCRYPTION_KEY); + + // Check if we need to refresh the token + try { + // Attempt to use the current token to see if it's still valid + await axios.get("https://api.twitter.com/2/users/me", { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }); + + // If no error is thrown, token is still valid + return tokens.accessToken; + } catch (error) { + // Token expired, refresh it + const newTokens = await refreshAccessToken(tokens.refreshToken); + + // Save new tokens + await saveTokens( + newTokens.accessToken, + newTokens.refreshToken, + env.ENCRYPTION_KEY + ); + + return newTokens.accessToken; + } + } catch (error) { + console.error("Error getting Twitter access token:", error); + throw new Error( + "Failed to get Twitter access token. Check if tokens are set up correctly." + ); + } +}; + +/** + * Refresh Twitter access token using a refresh token + * @param refreshToken - The Twitter API refresh token + * @returns Promise with updated access and refresh tokens + */ +export const refreshAccessToken = async ( + refreshToken: string +): Promise<{ + accessToken: string; + refreshToken: string; +}> => { + try { + const params = new URLSearchParams(); + params.append("refresh_token", refreshToken); + params.append("grant_type", "refresh_token"); + params.append("client_id", env.TWITTER_CLIENT_ID); + + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + params, + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + auth: { + username: env.TWITTER_CLIENT_ID, + password: env.TWITTER_CLIENT_SECRET, + }, + } + ); + + return { + accessToken: response.data.access_token, + refreshToken: response.data.refresh_token || refreshToken, + }; + } catch (error) { + console.error("Error refreshing access token:", error); + throw new Error("Failed to refresh Twitter access token"); + } +}; + +/** + * Generate text for a tweet using ChainGPT + * @param prompt - The prompt to generate tweet content + * @returns Promise with the generated tweet text + */ +export const getTextForTweet = async (prompt: string): Promise => { + try { + const response = await axios.post( + `${CHAINGPT_API_URL}/tweet-generator`, + { + prompt, + }, + { + headers: { + "Content-Type": "application/json", + "api-key": env.CHAINGPT_API_KEY, + }, + } + ); + let tweetText = response.data.tweet; + // let tweetText = response.data.tweet.slice(0, 270); // use this for base twitter api key + return tweetText; + } catch (error) { + console.error("Error generating tweet text:", error); + throw new Error("Failed to generate tweet content using ChainGPT API"); + } +}; + +/** + * Post a tweet to Twitter + * @param accessToken - The Twitter API access token + * @param message - The message to tweet + * @returns Promise with the Twitter API response + */ +export const postTweet = async ( + accessToken: string, + message: string +): Promise => { + try { + const response = await axios.post( + "https://api.twitter.com/2/tweets", + { + text: message, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + } + ); + return response.data; + } catch (error) { + console.error("Error posting tweet:", error); + throw error; + } +}; + +/** + * Generate a tweet based on prompt and post it to Twitter + * @param prompt - The prompt to generate tweet content + * @returns Promise with the Twitter API response and posted tweet + */ +export const generateAndPostTweet = async ( + prompt: string +): Promise<{ + response: TwitterPostResponse; + tweet: string; +}> => { + try { + const tweet = await getTextForTweet(prompt); + const accessToken = await getAccessToken(); + const response = await postTweet(accessToken, tweet); + + console.log(`Tweet posted successfully: ${tweet}`); + + return { + response, + tweet, + }; + } catch (error) { + console.error("Error generating and posting tweet:", error); + throw error; + } +}; + +/** + * Upload and post a tweet directly with given message + * @param message - The message to tweet + * @returns Promise with the Twitter API response + */ +export const uploadTwitterPostTweet = async ( + message: string +): Promise => { + try { + const accessToken = await getAccessToken(); + const response = await postTweet(accessToken, message); + + console.log(`Tweet posted successfully: ${message}`); + + return response; + } catch (error) { + console.error("Error posting tweet:", error); + throw error; + } +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..ed96f5b --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,88 @@ +/** + * Twitter API token interface + */ +export interface TwitterTokens { + accessToken: string; + refreshToken: string; +} + +/** + * Encrypted Twitter token interface + */ +export interface EncryptedTokens { + encryptedAccessToken: string; + encryptedRefreshToken: string; +} + +/** + * Twitter API OAuth response interface + */ +export interface TwitterOAuthResponse { + token_type: string; + expires_in: number; + access_token: string; + scope: string; + refresh_token?: string; +} + +/** + * Twitter API tweet post response interface + */ +export interface TwitterPostResponse { + data: { + id: string; + text: string; + }; +} + +/** + * Schedule configuration interface + */ +export interface ScheduleConfig { + config: { + persona: string; + maxLength: number; + timezone: string; + [key: string]: any; + }; + schedule: { + [timeKey: string]: { + type: string; + instruction: string; + [key: string]: any; + }; + }; +} + +/** + * Webhook registration request interface + */ +export interface WebhookRegistrationRequest { + url: string; +} + +/** + * Tweet webhook request interface + */ +export interface TweetWebhookRequest { + tweet: string; +} + +/** + * Token loading request interface + */ +export interface TokenLoadRequest { + accessToken: string; + refreshToken: string; +} + +/** + * API response interface for standardized responses + */ +export interface ApiResponse { + success: boolean; + message: string; + data?: T; + error?: string; +} + diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts new file mode 100644 index 0000000..c0ed359 --- /dev/null +++ b/src/utils/encryption.ts @@ -0,0 +1,159 @@ +import { join, dirname } from "path"; +import { env } from "../config/env"; +import { EncryptedTokens, TwitterTokens } from "../types"; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"; + +// File path for storing encrypted tokens +const TOKENS_FILE_PATH = join(import.meta.dir, "../../data/tokens.json"); + +// Encryption settings +const ALGORITHM = "AES-GCM"; // Using Web Crypto API algorithm name +const KEY_LENGTH = 32; // 256-bit key + +// Get salt and ENCRYPTION_IV from environment variables +const SALT = new Uint8Array(Buffer.from(env.ENCRYPTION_SALT, "hex")); +const ENCRYPTION_IV = new Uint8Array(Buffer.from(env.ENCRYPTION_IV, "hex")); + +/** + * Convert a string to a key using PBKDF2 + * @param encryptionKey - The encryption key from environment + * @returns A Promise containing the derived key + */ +async function deriveKey(encryptionKey: string): Promise { + // Import the encryption key as raw material + const encoder = new TextEncoder(); + const keyMaterial = await crypto.subtle.importKey( + "raw", + encoder.encode(encryptionKey), + "PBKDF2", + false, + ["deriveKey"] + ); + + // Derive a key using PBKDF2 + return crypto.subtle.deriveKey( + { + name: "PBKDF2", + salt: SALT, + iterations: 100000, + hash: "SHA-256", + }, + keyMaterial, + { name: ALGORITHM, length: KEY_LENGTH * 8 }, + false, + ["encrypt", "decrypt"] + ); +} + +/** + * Encrypt text data + * @param text - The text to encrypt + * @param encryptionKey - The encryption key + * @returns The encrypted text with authentication tag + */ +export async function encrypt( + text: string, + encryptionKey: string +): Promise { + const key = await deriveKey(encryptionKey); + const encoder = new TextEncoder(); + const data = encoder.encode(text); + + // Encrypt the data + const encryptedBuffer = await crypto.subtle.encrypt( + { + name: ALGORITHM, + iv: ENCRYPTION_IV, + }, + key, + data + ); + + // Convert the encrypted buffer to base64 + return Buffer.from(encryptedBuffer).toString("base64"); +} + +/** + * Decrypt encrypted text + * @param encryptedText - The encrypted text (base64 encoded) + * @param encryptionKey - The encryption key + * @returns The decrypted text + */ +export async function decrypt( + encryptedText: string, + encryptionKey: string +): Promise { + const key = await deriveKey(encryptionKey); + const encryptedBuffer = Buffer.from(encryptedText, "base64"); + + // Decrypt the data + const decryptedBuffer = await crypto.subtle.decrypt( + { + name: ALGORITHM, + iv: ENCRYPTION_IV, + }, + key, + encryptedBuffer + ); + + // Convert the decrypted buffer to string + const decoder = new TextDecoder(); + return decoder.decode(decryptedBuffer); +} + +/** + * Save Twitter tokens to file system in encrypted format + * @param accessToken - Twitter API access token + * @param refreshToken - Twitter API refresh token + * @param encryptionKey - The encryption key + */ +export async function saveTokens( + accessToken: string, + refreshToken: string, + encryptionKey: string +): Promise { + const encryptedAccessToken = await encrypt(accessToken, encryptionKey); + const encryptedRefreshToken = await encrypt(refreshToken, encryptionKey); + + const tokens: EncryptedTokens = { + encryptedAccessToken, + encryptedRefreshToken, + }; + + // Ensure directory exists + const dirPath = join(dirname(TOKENS_FILE_PATH)); + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { recursive: true }); + } + + writeFileSync(TOKENS_FILE_PATH, JSON.stringify(tokens, null, 2)); +} + +/** + * Load Twitter tokens from file system and decrypt them + * @param encryptionKey - The encryption key + * @returns The decrypted Twitter tokens + */ +export async function loadTokens( + encryptionKey: string +): Promise { + if (!existsSync(TOKENS_FILE_PATH)) { + throw new Error( + "Twitter tokens file not found. Please set up tokens first." + ); + } + + const tokensData = readFileSync(TOKENS_FILE_PATH, "utf8"); + const encryptedTokens: EncryptedTokens = JSON.parse(tokensData); + + const accessToken = await decrypt( + encryptedTokens.encryptedAccessToken, + encryptionKey + ); + const refreshToken = await decrypt( + encryptedTokens.encryptedRefreshToken, + encryptionKey + ); + + return { accessToken, refreshToken }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b6a1343 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["bun-types"], + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true + } +} \ No newline at end of file diff --git a/twitterTokenGeneration.md b/twitterTokenGeneration.md new file mode 100644 index 0000000..fbbb784 --- /dev/null +++ b/twitterTokenGeneration.md @@ -0,0 +1,241 @@ +# ๐Ÿ”‘ Twitter Token Generation Guide + +This guide provides a complete solution for generating Twitter OAuth 2.0 access and refresh tokens using Express.js. + +## ๐Ÿ“‹ Prerequisites + +- Node.js (v14 or newer) +- npm or yarn +- Twitter Developer Account with OAuth 2.0 credentials + +## ๐Ÿ› ๏ธ Installation + +```bash +# Create a new directory +mkdir twitter-token-generator +cd twitter-token-generator + +# Initialize a new Node.js project +npm init -y + +# Install required dependencies +npm install express axios crypto querystring express-session +npm install --save-dev typescript ts-node @types/express @types/express-session + +# Create TypeScript configuration +npx tsc --init +``` + +## ๐Ÿ“ Code Implementation + +### ๐Ÿ”ง Imports and Type Definitions + +```typescript +// Required imports +import express from "express"; +import axios from "axios"; +import crypto from "crypto"; +import querystring from "querystring"; +import session from "express-session"; +import { Buffer } from "buffer"; + +// Type definitions +type Request = express.Request; +type Response = express.Response; + +declare module "express-session" { + interface Session { + codeVerifier: string; + } +} + +interface TwitterTokens { + access_token: string; + refresh_token: string; +} +``` + +### โš™๏ธ Configuration + +```typescript +// Configuration +const config = { + clientId: "your_twitter_client_id", //twitter client id + clientSecret: "your_twitter_client_secret", // twitter secret + redirectUri: "http://localhost:8000/callback", //change port or domain according to your configurations + port: 8000, + sessionSecret: "your_session_secret", //set a value and keep it secure +}; + +// Initialize Express app +const app = express(); + +// Session middleware +app.use( + session({ + secret: config.sessionSecret, + resave: false, + saveUninitialized: true, + }) +); +``` + +### ๐Ÿ” PKCE Generation + +```typescript +// Generate PKCE code verifier and challenge +const generatePKCE = (): { codeVerifier: string; codeChallenge: string } => { + const codeVerifier = crypto.randomBytes(32).toString("base64url"); + const codeChallenge = crypto + .createHash("sha256") + .update(codeVerifier) + .digest("base64url"); + return { codeVerifier, codeChallenge }; +}; +``` + +### ๐Ÿ”— OAuth Endpoints + +#### Login Route + +```typescript +// Login route - initiates OAuth flow +app.get("/login", (req: Request, res: Response): void => { + const { codeVerifier, codeChallenge } = generatePKCE(); + const state = crypto.randomBytes(16).toString("hex"); + + // Store code verifier in session + req.session.codeVerifier = codeVerifier; + + const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify( + { + response_type: "code", + client_id: config.clientId, + redirect_uri: config.redirectUri, + scope: "tweet.read users.read tweet.write offline.access", + state, + code_challenge: codeChallenge, + code_challenge_method: "S256", + } + )}`; + + res.redirect(authorizationUrl); +}); +``` + +#### Callback Route + +```typescript +// Callback route - handles OAuth response +app.get("/callback", async (req: Request, res: Response): Promise => { + const code = req.query.code as string; + const codeVerifier = req.session.codeVerifier; + + if (!code || !codeVerifier) { + res.status(400).send("Authorization failed: Missing code or verifier"); + return; + } + + const basicAuth = Buffer.from( + `${config.clientId}:${config.clientSecret}` + ).toString("base64"); + + try { + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + querystring.stringify({ + code, + client_id: config.clientId, + client_secret: config.clientSecret, + redirect_uri: config.redirectUri, + code_verifier: codeVerifier, + grant_type: "authorization_code", + }), + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${basicAuth}`, + }, + } + ); + + const { access_token, refresh_token } = response.data; + console.log( + `Access and refresh tokens received: ${JSON.stringify( + { access_token, refresh_token }, + null, + 2 + )}` + ); + res.send( + `Access and refresh tokens received: ${JSON.stringify( + { access_token, refresh_token }, + null, + 2 + )}` + ); + } catch (error: any) { + if (axios.isAxiosError(error)) { + res + .status(500) + .send( + `Error during the token exchange: ${JSON.stringify( + error.response?.data || error.message + )}` + ); + } else { + res.status(500).send("An unexpected error occurred"); + } + } +}); +``` + +### ๐Ÿš€ Server Startup + +```typescript +// Start the server +app.listen(config.port, () => { + console.log( + `Access and Refresh Token Generator listening on port ${config.port}` + ); +}); +``` + +## ๐Ÿ“‹ Complete File + +Save all the code above to a file named `twitter-token-generator.ts`. + +## ๐Ÿš€ Running the Application + +```bash +# Run the application +node --loader ts-node/esm twitter-token-generator.ts +``` + +## ๐Ÿ”„ Usage Flow + +1. Visit `http://localhost:8000/login` in your browser +2. You'll be redirected to Twitter's authorization page +3. Log in and authorize the application +4. You'll be redirected back to your application +5. The access and refresh tokens will be displayed on the page + +## ๐Ÿ” Security Considerations + +- Store your client ID and client secret securely +- Use HTTPS in production +- Implement proper session management +- Consider using a more robust storage solution for the code verifier in production + +## ๐Ÿ”— Integration with AgenticOS + +After obtaining your tokens, you can add them to your AgenticOS application using the following endpoint: + +```bash +POST /api/tokens + +Body: { + "accessToken": "your_access_token", + "refreshToken": "your_refresh_token" +} +``` From f32cdd62eeb8a83443adc98cadfe39df3992dc07 Mon Sep 17 00:00:00 2001 From: sorooj Date: Fri, 18 Apr 2025 15:07:12 +0500 Subject: [PATCH 026/100] fixes --- README.md | 16 +++++++++++++--- twitterTokenGeneration.md | 8 ++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e9e744d..b98def0 100644 --- a/README.md +++ b/README.md @@ -126,13 +126,20 @@ Tweets are auto-generated and posted according to this schedule (UTC). ```bash GET https://webapi.chaingpt.org/category-subscription/ +Headers: +{ + "api-key": "" +} ``` - Subscribe to categories: ```bash POST https://webapi.chaingpt.org/category-subscription/subscribe - +Headers: +{ + "api-key": "" +} Body: { "categoryIds": [2, 3] } ``` @@ -141,8 +148,11 @@ Body: { "categoryIds": [2, 3] } Register your webhook to automatically post updates: ```bash -POST {https://your-domain.com}/api/webhook/register - +POST https://your-domain.com}/api/webhook/register +Headers: +{ + "api-key": "" +} Body: { "url": "{https://your-domain.com}/api/webhook/" } ``` diff --git a/twitterTokenGeneration.md b/twitterTokenGeneration.md index fbbb784..17a003d 100644 --- a/twitterTokenGeneration.md +++ b/twitterTokenGeneration.md @@ -62,8 +62,8 @@ interface TwitterTokens { const config = { clientId: "your_twitter_client_id", //twitter client id clientSecret: "your_twitter_client_secret", // twitter secret - redirectUri: "http://localhost:8000/callback", //change port or domain according to your configurations - port: 8000, + redirectUri: "http://localhost:3000/callback", //change port or domain according to your configurations + port: 3000, sessionSecret: "your_session_secret", //set a value and keep it secure }; @@ -209,12 +209,12 @@ Save all the code above to a file named `twitter-token-generator.ts`. ```bash # Run the application -node --loader ts-node/esm twitter-token-generator.ts +npx ts-node twitter-token-generator.ts ``` ## ๐Ÿ”„ Usage Flow -1. Visit `http://localhost:8000/login` in your browser +1. Visit `http://localhost:3000/login` in your browser 2. You'll be redirected to Twitter's authorization page 3. Log in and authorize the application 4. You'll be redirected back to your application From 122e84c04ad0e81567e5e08432bdab93638a98d8 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 09:29:41 +0500 Subject: [PATCH 027/100] Refresh and access token is created in one api code --- bun.lock | 13 +++- package.json | 1 + src/controllers/login.controllers.ts | 104 +++++++++++++++++++++++++++ src/index.ts | 1 + src/routes/index.ts | 3 + src/routes/login.routes.ts | 10 +++ tsconfig.json | 2 +- 7 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 src/controllers/login.controllers.ts create mode 100644 src/routes/login.routes.ts diff --git a/bun.lock b/bun.lock index 58b3b6b..97112ba 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "zod": "^3.22.4", }, "devDependencies": { + "@types/node": "^22.14.1", "@types/node-cron": "^3.0.11", "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", @@ -41,7 +42,7 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="], + "@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], "@types/node-cron": ["@types/node-cron@3.0.11", "", {}, "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="], @@ -317,7 +318,7 @@ "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], - "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], @@ -333,10 +334,18 @@ "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + "@types/ws/@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "bun-types/@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], } } diff --git a/package.json b/package.json index e39a20c..cd7e891 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "zod": "^3.22.4" }, "devDependencies": { + "@types/node": "^22.14.1", "@types/node-cron": "^3.0.11", "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts new file mode 100644 index 0000000..cbd952c --- /dev/null +++ b/src/controllers/login.controllers.ts @@ -0,0 +1,104 @@ +import { Context } from "hono"; +import axios from "axios"; +import crypto from "crypto"; +import querystring from "querystring"; +import { Buffer } from "buffer"; +import { getCookie, setCookie } from "hono/cookie"; + +interface TwitterTokens { + access_token: string; + refresh_token: string; +} + + +// Configuration โ€“ replace with your Twitter app credentials +const config = { + clientId: process.env.TWITTER_CLIENT_ID, // Twitter OAuth2 Client ID + clientSecret: process.env.TWITTER_CLIENT_SECRET, // Twitter OAuth2 Client Secret + redirectUri: "http://localhost:8000/api/login/callback", // Must match callback in Twitter app settings + port: 8000, +}; + +// Generate PKCE code verifier and challenge +const generatePKCE = (): { codeVerifier: string; codeChallenge: string } => { + const codeVerifier = crypto.randomBytes(32).toString("base64url"); + const codeChallenge = crypto + .createHash("sha256") + .update(codeVerifier) + .digest("base64url"); + return { codeVerifier, codeChallenge }; +}; + +// Login route โ€“ initiates the OAuth flow +export const login = async (c: Context) => { + const { codeVerifier, codeChallenge } = generatePKCE(); + const state = crypto.randomBytes(16).toString("hex"); + + // Store the code verifier in a cookie + setCookie(c, "codeVerifier", codeVerifier, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "Lax", + maxAge: 5 * 60 * 1000 // 5 minutes + }); + + // Redirect to Twitter's OAuth 2.0 authorization endpoint + const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify({ + response_type: "code", + client_id: config.clientId, + redirect_uri: config.redirectUri, + scope: "tweet.read users.read tweet.write offline.access", + state: state, + code_challenge: codeChallenge, + code_challenge_method: "S256", + })}`; + + return c.redirect(authorizationUrl); +}; + +// Callback route โ€“ handles Twitter's redirect back to our app +export const callback = async (c: Context) => { + const code = c.req.query("code"); + const codeVerifier = getCookie(c, "codeVerifier"); + if (!code || !codeVerifier) { + return c.json({ error: "Authorization failed: Missing code or verifier" }, 400); + } + // Prepare Basic auth header for Twitter token request + const basicAuth = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64"); + + try { + // Exchange the authorization code for access and refresh tokens + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + querystring.stringify({ + code: code, + client_id: config.clientId, + client_secret: config.clientSecret, + redirect_uri: config.redirectUri, + code_verifier: codeVerifier, + grant_type: "authorization_code", + }), + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": `Basic ${basicAuth}`, + }, + } + ); + + const { access_token, refresh_token } = response.data; + console.log("Access and refresh tokens received:", { access_token, refresh_token }); + return c.json({ access_token, refresh_token }); + } catch (error: any) { + if (axios.isAxiosError(error)) { + return c.json({ error: `Error during the token exchange: ${JSON.stringify(error.response?.data || error.message)}` }, 500); + } else { + return c.json({ error: "An unexpected error occurred" }, 500); + } + } +}; + +// // Start the server to listen for OAuth requests +// app.listen(config.port, () => { +// console.log(`Token generator listening on port ${config.port}`); +// }); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 47bce41..ee07058 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { env } from "./config/env"; import apiRouter from "./routes"; import { scheduleTweets } from "./jobs/tweet.job"; + // Create Hono app const app = new Hono(); diff --git a/src/routes/index.ts b/src/routes/index.ts index 4fa71e1..1c73442 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,6 +1,8 @@ import { Hono } from 'hono'; import webhookRouter from './webhook.routes'; import tokenRouter from './token.routes'; +import loginRouter from '../routes/login.routes'; + // Create a Hono router for API routes const apiRouter = new Hono(); @@ -8,5 +10,6 @@ const apiRouter = new Hono(); // Register API routes apiRouter.route('/webhook', webhookRouter); apiRouter.route('/tokens', tokenRouter); +apiRouter.route('/login', loginRouter); export default apiRouter; \ No newline at end of file diff --git a/src/routes/login.routes.ts b/src/routes/login.routes.ts new file mode 100644 index 0000000..6a8d547 --- /dev/null +++ b/src/routes/login.routes.ts @@ -0,0 +1,10 @@ +import { Hono } from "hono"; +import { login, callback } from "../controllers/login.controllers"; + +const router = new Hono(); + +// Login routes +router.get("/", login); +router.get("/callback", callback); + +export default router; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index b6a1343..3359949 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "types": ["bun-types"], + "types": ["bun-types", "node"], "target": "ESNext", "module": "ESNext", "moduleResolution": "node", From 1cc960f9d3bd15de579c5b314b13c85421cd9115 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 09:33:54 +0500 Subject: [PATCH 028/100] One Click deployment on Render is implemented --- README.md | 3 +++ render.yaml | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 render.yaml diff --git a/README.md b/README.md index b98def0..5cfd589 100644 --- a/README.md +++ b/README.md @@ -224,3 +224,6 @@ Contributions are welcome! Follow these steps: Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-agent/issues). ๐Ÿš€ **Happy Coding!** + +# Deploy on Render on One Click +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..a4c3e44 --- /dev/null +++ b/render.yaml @@ -0,0 +1,29 @@ +services: + - type: web + name: agenticos-app + env: node + branch: dev + repo: https://github.com/nasirfunavry/AgenticOS + buildCommand: bun install + startCommand: bun start + autoDeploy: true + envVars: + - key: PORT + value: 8000 + - key: NODE_ENV + value: development + + - key: TWITTER_CLIENT_ID + sync: false + - key: TWITTER_CLIENT_SECRET + sync: false + + - key: ENCRYPTION_KEY + sync: false + - key: ENCRYPTION_SALT + sync: false + - key: ENCRYPTION_IV + sync: false + + - key: CHAINGPT_API_KEY + sync: false From 4ad501c8305cb5346d73ac303850bef7ba303fc6 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 09:39:00 +0500 Subject: [PATCH 029/100] Added to vercel for one click deployment --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5cfd589..9d70264 100644 --- a/README.md +++ b/README.md @@ -227,3 +227,6 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age # Deploy on Render on One Click [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) + +# Deploy on Vercel on One Click +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev) From 735a81fae1ad8b6e319a05b34fa8ac039ab2961c Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 09:54:19 +0500 Subject: [PATCH 030/100] Readme and vercel json added --- README.md | 2 +- vercel.json | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 vercel.json diff --git a/README.md b/README.md index 9d70264..08af5a9 100644 --- a/README.md +++ b/README.md @@ -229,4 +229,4 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) # Deploy on Vercel on One Click -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev) \ No newline at end of file diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..13ff9a2 --- /dev/null +++ b/vercel.json @@ -0,0 +1,9 @@ +{ + "buildCommand": "bun install", + "outputDirectory": ".", + "devCommand": "bun start", + "env": { + "NODE_ENV": "production" + } + } + \ No newline at end of file From e561b4aa38b308ef25bc01a89726b4f7de934c06 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:10:36 +0500 Subject: [PATCH 031/100] add forked ref to detect proper branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08af5a9..307c5e3 100644 --- a/README.md +++ b/README.md @@ -229,4 +229,4 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) # Deploy on Vercel on One Click -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev) \ No newline at end of file +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChainGPT-org%2FAgenticOS&fork-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev) \ No newline at end of file From 4fc9cf53dd8e376a003459df8d8cd9e8f01552d2 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:12:01 +0500 Subject: [PATCH 032/100] Add branch to main --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 307c5e3..4edd446 100644 --- a/README.md +++ b/README.md @@ -229,4 +229,4 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) # Deploy on Vercel on One Click -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChainGPT-org%2FAgenticOS&fork-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev) \ No newline at end of file +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=main) \ No newline at end of file From 275923b50e696a2d719ce1bc105b1ee725fdd07f Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:23:55 +0500 Subject: [PATCH 033/100] update link to deployment --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4edd446..9cc22b7 100644 --- a/README.md +++ b/README.md @@ -229,4 +229,4 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) # Deploy on Vercel on One Click -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=main) \ No newline at end of file +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) From a96f28b6eae72df846f2d0de21a355aad5490b63 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:37:07 +0500 Subject: [PATCH 034/100] update link --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cc22b7..78a34dd 100644 --- a/README.md +++ b/README.md @@ -229,4 +229,6 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) # Deploy on Vercel on One Click -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) + + From 120d9dd5986554864a8ce5f5ce5f17ca13b5fef2 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:45:39 +0500 Subject: [PATCH 035/100] Configuration json for vercel --- vercel.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vercel.json b/vercel.json index 13ff9a2..7c5686c 100644 --- a/vercel.json +++ b/vercel.json @@ -1,9 +1,6 @@ { - "buildCommand": "bun install", - "outputDirectory": ".", - "devCommand": "bun start", - "env": { - "NODE_ENV": "production" - } + "buildCommand": "bun install", + "devCommand": "bun run dev", + "framework": "other" } \ No newline at end of file From 2c0393c011693806008f313df476fadfdc3e9f9b Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:47:08 +0500 Subject: [PATCH 036/100] return back --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78a34dd..f389297 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,6 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) # Deploy on Vercel on One Click -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS&branch=dev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) + - +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) From 7207678be18f40d5c32c0a27d6276efebdf351c9 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:51:20 +0500 Subject: [PATCH 037/100] remove vercel josn file --- vercel.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 vercel.json diff --git a/vercel.json b/vercel.json deleted file mode 100644 index 7c5686c..0000000 --- a/vercel.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "buildCommand": "bun install", - "devCommand": "bun run dev", - "framework": "other" - } - \ No newline at end of file From 069cadd2cddc31d5d573c818adae6629af5b1edc Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:53:32 +0500 Subject: [PATCH 038/100] vercel json with fields back --- vercel.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..b10338d --- /dev/null +++ b/vercel.json @@ -0,0 +1,5 @@ +{ + "buildCommand": "bun install", + "devCommand": "bun run dev", + "framework": "node" + } \ No newline at end of file From e97f138f4dc49d30c858c82be6c6cf5276f61bd0 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:55:50 +0500 Subject: [PATCH 039/100] change framework from node to vite --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index b10338d..4984b7c 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,5 @@ { "buildCommand": "bun install", "devCommand": "bun run dev", - "framework": "node" + "framework": "vite" } \ No newline at end of file From 495e2283503063e2a2612098e93976c8d4cfb70c Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 10:58:03 +0500 Subject: [PATCH 040/100] bun update with commands --- vercel.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vercel.json b/vercel.json index 4984b7c..348f4dd 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,6 @@ { - "buildCommand": "bun install", + "buildCommand": "bun install && bun run build", "devCommand": "bun run dev", - "framework": "vite" + "framework": "vite", + "outputDirectory": "src" } \ No newline at end of file From 862829e8c65e660d596e3cc19b7ea4968587ab2a Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 11:05:52 +0500 Subject: [PATCH 041/100] vercel package updated --- vercel.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vercel.json b/vercel.json index 348f4dd..14a3e93 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "bun install && bun run build", "devCommand": "bun run dev", - "framework": "vite", - "outputDirectory": "src" + "outputDirectory": "dist", + "framework": "nodejs" } \ No newline at end of file From 6d27ddb93dcb8e842f7621dbe21bc0263756ae39 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 11:07:39 +0500 Subject: [PATCH 042/100] remove framework --- vercel.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vercel.json b/vercel.json index 14a3e93..bfcdfec 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,5 @@ { "buildCommand": "bun install && bun run build", "devCommand": "bun run dev", - "outputDirectory": "dist", - "framework": "nodejs" + "outputDirectory": "dist" } \ No newline at end of file From a3aecc236260ef1f9a8325166c5cd97cabb2d8c5 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 11:13:14 +0500 Subject: [PATCH 043/100] add builds and routes --- vercel.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index bfcdfec..f2b1170 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,17 @@ { "buildCommand": "bun install && bun run build", "devCommand": "bun run dev", - "outputDirectory": "dist" + "outputDirectory": "dist", + "builds": [ + { + "src": "src/index.ts", + "use": "@vercel/node" + } + ], + "routes": [ + { + "src": "/(.*)", + "dest": "src/index.ts" + } + ] } \ No newline at end of file From b22e1bc105b6a42432593b77224dfe38f8c4572c Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 11:36:21 +0500 Subject: [PATCH 044/100] one click deployment on railway --- README.md | 3 +++ railway.json | 12 ++++++++++++ tsconfig.json | 11 +++++++---- vercel.json | 19 ++++++------------- 4 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 railway.json diff --git a/README.md b/README.md index f389297..d471398 100644 --- a/README.md +++ b/README.md @@ -232,3 +232,6 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) + +# Deploy on Railway on One Click +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&envs=PORT%2CNODE_ENV%2CTWITTER_CLIENT_ID%2CTWITTER_CLIENT_SECRET%2CENCRYPTION_KEY%2CENCRYPTION_SALT%2CENCRYPTION_IV%2CCHAINGPT_API_KEY&optionalEnvs=PORT&PORTDefault=8000&NODE_ENVDefault=development) \ No newline at end of file diff --git a/railway.json b/railway.json new file mode 100644 index 0000000..2f221b6 --- /dev/null +++ b/railway.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "NIXPACKS", + "buildCommand": "bun install" + }, + "deploy": { + "startCommand": "bun run src/index.ts", + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 10 + } + } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3359949..44c0607 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,13 @@ { "compilerOptions": { - "types": ["bun-types", "node"], - "target": "ESNext", + "target": "ES2022", "module": "ESNext", "moduleResolution": "node", "esModuleInterop": true, - "strict": true - } + "strict": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] } \ No newline at end of file diff --git a/vercel.json b/vercel.json index f2b1170..c28ce22 100644 --- a/vercel.json +++ b/vercel.json @@ -1,17 +1,10 @@ { - "buildCommand": "bun install && bun run build", + "buildCommand": "bun install", "devCommand": "bun run dev", "outputDirectory": "dist", - "builds": [ - { - "src": "src/index.ts", - "use": "@vercel/node" - } - ], - "routes": [ - { - "src": "/(.*)", - "dest": "src/index.ts" - } - ] + "functions": { + "src/index.ts": { + "runtime": "@vercel/node@latest" + } + } } \ No newline at end of file From 0617f4ba243e001bdf996e1ed7af5437cf05c1b0 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 11:40:07 +0500 Subject: [PATCH 045/100] sugesting added --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d471398..6df5814 100644 --- a/README.md +++ b/README.md @@ -234,4 +234,6 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) # Deploy on Railway on One Click -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&envs=PORT%2CNODE_ENV%2CTWITTER_CLIENT_ID%2CTWITTER_CLIENT_SECRET%2CENCRYPTION_KEY%2CENCRYPTION_SALT%2CENCRYPTION_IV%2CCHAINGPT_API_KEY&optionalEnvs=PORT&PORTDefault=8000&NODE_ENVDefault=development) \ No newline at end of file +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&envs=PORT%2CNODE_ENV%2CTWITTER_CLIENT_ID%2CTWITTER_CLIENT_SECRET%2CENCRYPTION_KEY%2CENCRYPTION_SALT%2CENCRYPTION_IV%2CCHAINGPT_API_KEY&optionalEnvs=PORT&PORTDefault=8000&NODE_ENVDefault=development) + + \ No newline at end of file From c59e0e76aef3eb243f60232bc41eaf3c3b8d33f7 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Fri, 25 Apr 2025 11:43:30 +0500 Subject: [PATCH 046/100] rawily setup added --- README.md | 2 +- railway.json | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6df5814..c5b3850 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,6 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) # Deploy on Railway on One Click -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&envs=PORT%2CNODE_ENV%2CTWITTER_CLIENT_ID%2CTWITTER_CLIENT_SECRET%2CENCRYPTION_KEY%2CENCRYPTION_SALT%2CENCRYPTION_IV%2CCHAINGPT_API_KEY&optionalEnvs=PORT&PORTDefault=8000&NODE_ENVDefault=development) +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https://github.com/nasirfunavry/AgenticOS&branch=dev&envs=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDesc=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.) \ No newline at end of file diff --git a/railway.json b/railway.json index 2f221b6..51f49fa 100644 --- a/railway.json +++ b/railway.json @@ -1,12 +1,14 @@ { - "$schema": "https://railway.app/railway.schema.json", "build": { - "builder": "NIXPACKS", - "buildCommand": "bun install" + "env": { + "PORT": "8000", + "NODE_ENV": "development" + }, + "cmd": "curl -fsSL https://bun.sh/install | bash && bun install" }, - "deploy": { - "startCommand": "bun run src/index.ts", - "restartPolicyType": "ON_FAILURE", - "restartPolicyMaxRetries": 10 - } - } \ No newline at end of file + "start": { + "cmd": "bun run dev" + }, + "plugins": [] + } + \ No newline at end of file From f3f4706426655451770203b6fb0084a78e1e50f4 Mon Sep 17 00:00:00 2001 From: nasirfunavry Date: Fri, 25 Apr 2025 11:46:20 +0500 Subject: [PATCH 047/100] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c5b3850..5e5ad06 100644 --- a/README.md +++ b/README.md @@ -236,4 +236,5 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age # Deploy on Railway on One Click [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https://github.com/nasirfunavry/AgenticOS&branch=dev&envs=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDesc=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.) - \ No newline at end of file + + From 58725146f8402a97401d1d3cda5d59ee0a85cde6 Mon Sep 17 00:00:00 2001 From: nasirfunavry Date: Fri, 25 Apr 2025 11:47:54 +0500 Subject: [PATCH 048/100] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e5ad06..15ca8be 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,6 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) # Deploy on Railway on One Click -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https://github.com/nasirfunavry/AgenticOS&branch=dev&envs=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDesc=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.) - +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/project?template=https://github.com/nasirfunavry/AgenticOS&branch=dev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.) From ba58d65a0c63a762fb162b5749a1f6b54bbd5011 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Mon, 28 Apr 2025 15:14:33 +0500 Subject: [PATCH 049/100] Update Callback Url --- src/controllers/login.controllers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts index cbd952c..6fe2b37 100644 --- a/src/controllers/login.controllers.ts +++ b/src/controllers/login.controllers.ts @@ -15,7 +15,7 @@ interface TwitterTokens { const config = { clientId: process.env.TWITTER_CLIENT_ID, // Twitter OAuth2 Client ID clientSecret: process.env.TWITTER_CLIENT_SECRET, // Twitter OAuth2 Client Secret - redirectUri: "http://localhost:8000/api/login/callback", // Must match callback in Twitter app settings + redirectUri: "https://agenticos-app.onrender.com/api/login/callback", // Must match callback in Twitter app settings port: 8000, }; From a0fe828b970e3bd13e287c85d16ad033b934ac97 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Mon, 28 Apr 2025 15:47:22 +0500 Subject: [PATCH 050/100] update twitter sheduler --- data/schedule.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/data/schedule.json b/data/schedule.json index 0428803..35ec87b 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -41,6 +41,38 @@ "type": "security_tip", "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." }, + "10:48": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "10:52": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "10:58": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "11:00": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "11:05": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "11:10": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "11:15": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, + "11:20": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, "13:30": { "type": "market_analysis", "instruction": "{{persona}} and excellent at market analysis. Create a crypto market analysis report showing all the crypto related indicators that could be helpful. Start the report with: Here's a Comprehensive Analysis Report on Key Crypto Market Indicators: (Expand the tweet to view it in full ๐Ÿ‘‡)" From 2e195ac0534b9cf9c05480cd0517fc0463f7f14b Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 09:24:27 +0500 Subject: [PATCH 051/100] Update custome tweet sheduler --- data/schedule.json | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/data/schedule.json b/data/schedule.json index 35ec87b..efa12cb 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -21,18 +21,74 @@ "type": "market_recap", "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." }, + "04:50": { + "type": "market_recap", + "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." + }, + "05:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "05:30": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, +"05:50": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "06:00": { "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, +"07:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:30": { "type": "market_insight", "instruction": "{{persona}} and good at market analysis. Write something about the market based on the fear & greed index and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters." }, + "07:50": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, +"08:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, +"08:20": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "08:30": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + + "09:00": { "type": "news", "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." }, + "09:30": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "09:50": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "10:00": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "10:20": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, "10:30": { "type": "nft", "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the most hyped NFT collections and their volumes." From 2e94fd01ca475c41d4e9c93b75acf0db93e7e583 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 16:14:07 +0500 Subject: [PATCH 052/100] Update set token with password only --- flow.svg | 121 +++++++++++++++++++++++++++ src/config/env.ts | 3 + src/controllers/login.controllers.ts | 1 + src/controllers/token.controller.ts | 18 +++- src/types/index.ts | 1 + 5 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 flow.svg diff --git a/flow.svg b/flow.svg new file mode 100644 index 0000000..af9f1a3 --- /dev/null +++ b/flow.svg @@ -0,0 +1,121 @@ + + + + + + Twitter Automation App Workflow + + + + Deployment Phase + + + + 1. One-Click Deployment + Initiate deployment process + + + + 2. App Configuration + Name the app + Set environment variables + + + + 3. App Deployed + Get app URL + + + + 4. Twitter Auth + Call Twitter login + Get access & refresh tokens + + + + + + + + + Customization Phase + + + + 5. Clone Repository + Clone app repo from +GitHub for customization + + + + 6. Update Schedule + Update schedule.json + Push changes & wait + + + + 7. Set Tokens + Update tokens.json + Set access & refresh + tokens via API + + + + 8. Setup Complete + App ready for + scheduled tweeting + + + + + + + + + Webhook Configuration Phase + + + + 9. Webhook Setup + Configure event listener + for Twitter events + + + + 10. Get Categories + Get all categories from + category-subscription + View subscribed categories + + + + 11. Subscribe + Subscribe to ChatGPT + categories + + + + 12-13. Webhook Events + Register webhook + Listen for events + Post tweets automatically + + + + + + + + + + + + + + + + + + + Twitter Automation Application Workflow Diagram + \ No newline at end of file diff --git a/src/config/env.ts b/src/config/env.ts index edabea4..9739fca 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -21,6 +21,9 @@ const envSchema = z.object({ // ChainGPT API CHAINGPT_API_KEY: z.string().min(1, "ChainGPT API key is required"), + + // Token Set Password + PASSWORD_AUTH: z.string().min(1, "Token set password is required"), }); // Parse and validate environment variables diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts index 6fe2b37..afce98c 100644 --- a/src/controllers/login.controllers.ts +++ b/src/controllers/login.controllers.ts @@ -88,6 +88,7 @@ export const callback = async (c: Context) => { const { access_token, refresh_token } = response.data; console.log("Access and refresh tokens received:", { access_token, refresh_token }); + return c.json({ access_token, refresh_token }); } catch (error: any) { if (axios.isAxiosError(error)) { diff --git a/src/controllers/token.controller.ts b/src/controllers/token.controller.ts index ed0ae5e..7337e41 100644 --- a/src/controllers/token.controller.ts +++ b/src/controllers/token.controller.ts @@ -10,19 +10,29 @@ import { ApiResponse, TokenLoadRequest } from "../types"; */ export const loadTokens = async (c: Context): Promise => { try { - const { accessToken, refreshToken } = await c.req.json(); + const { accessToken, refreshToken, password } = await c.req.json(); - if (!accessToken || !refreshToken) { + if (!accessToken || !refreshToken || !password) { return c.json( { success: false, - message: "Access token and refresh token are required", - error: "Missing required fields: accessToken and/or refreshToken", + message: "Access token, refresh token and password are required", + error: "Missing required fields: accessToken and/or refreshToken and/or password", }, 400 ); } + // Verify password + const isPasswordValid = password === env.PASSWORD_AUTH; + // If using bcrypt: + // const isPasswordValid = await compare(body.password, env.TOKEN_SET_PASSWORD); + if (!isPasswordValid) { + return c.json({ + success: false, + message: "Unauthorized: Invalid password" + }, 401); + } await saveTokens(accessToken, refreshToken, env.ENCRYPTION_KEY); return c.json({ diff --git a/src/types/index.ts b/src/types/index.ts index ed96f5b..490b9d1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -74,6 +74,7 @@ export interface TweetWebhookRequest { export interface TokenLoadRequest { accessToken: string; refreshToken: string; + password: string; } /** From d0d7b619f4f89a3e2f261bfb5e46c2736f4fd1ef Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 16:15:52 +0500 Subject: [PATCH 053/100] ignore others to show on readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 15ca8be..d008a89 100644 --- a/README.md +++ b/README.md @@ -228,12 +228,12 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age # Deploy on Render on One Click [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) -# Deploy on Vercel on One Click + -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Ftree%2Fdev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.&envLink=https%3A%2F%2Fgithub.com%2Fnasirfunavry%2FAgenticOS%2Fblob%2Fdev%2F.env.example&project-name=agenticos&repository-name=AgenticOS) + -# Deploy on Railway on One Click -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/project?template=https://github.com/nasirfunavry/AgenticOS&branch=dev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.) + From d2c1c23d26ade060c0d284fb681fb0514d3481bc Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 18:10:34 +0500 Subject: [PATCH 054/100] On Login Auth it directly Set access and Refresh Token --- README.md | 14 +++++++++++++ src/controllers/login.controllers.ts | 31 ++++++++++++++++++++++++---- src/utils/encryption.ts | 30 ++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d008a89..c4c706c 100644 --- a/README.md +++ b/README.md @@ -237,3 +237,17 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/project?template=https://github.com/nasirfunavry/AgenticOS&branch=dev&env=PORT,NODE_ENV,TWITTER_CLIENT_ID,TWITTER_CLIENT_SECRET,ENCRYPTION_KEY,ENCRYPTION_SALT,ENCRYPTION_IV,CHAINGPT_API_KEY&envDescription=Add%20required%20API%20keys%20and%20secrets%20from%20Twitter%20and%20ChainGPT.%20See%20.env.example%20for%20reference.) --> + + + + + + + + diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts index afce98c..9bcd4e0 100644 --- a/src/controllers/login.controllers.ts +++ b/src/controllers/login.controllers.ts @@ -4,6 +4,8 @@ import crypto from "crypto"; import querystring from "querystring"; import { Buffer } from "buffer"; import { getCookie, setCookie } from "hono/cookie"; +import { env } from '../config/env'; +import { saveTokens , tokenAlreadyExists} from "../utils/encryption"; interface TwitterTokens { access_token: string; @@ -15,7 +17,7 @@ interface TwitterTokens { const config = { clientId: process.env.TWITTER_CLIENT_ID, // Twitter OAuth2 Client ID clientSecret: process.env.TWITTER_CLIENT_SECRET, // Twitter OAuth2 Client Secret - redirectUri: "https://agenticos-app.onrender.com/api/login/callback", // Must match callback in Twitter app settings + redirectUri: "http://localhost:8000/api/login/callback",//https://agenticos-app.onrender.com/api/login/callback", // Must match callback in Twitter app settings port: 8000, }; @@ -41,7 +43,7 @@ export const login = async (c: Context) => { sameSite: "Lax", maxAge: 5 * 60 * 1000 // 5 minutes }); - +console.log("config.redirectUri,",config.redirectUri,) // Redirect to Twitter's OAuth 2.0 authorization endpoint const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify({ response_type: "code", @@ -88,8 +90,29 @@ export const callback = async (c: Context) => { const { access_token, refresh_token } = response.data; console.log("Access and refresh tokens received:", { access_token, refresh_token }); - - return c.json({ access_token, refresh_token }); + // Directly call loadTokens with the context + const isAlreadyExist = await tokenAlreadyExists(); + + if (!isAlreadyExist) { + await saveTokens(access_token, refresh_token, env.ENCRYPTION_KEY); + return c.json({ + success: true, + message: "OAuth access token and refresh token have been saved successfully", + access_token, + refresh_token, + status: "new_tokens_saved" + }); + } else { + return c.json({ + success: true, + message: "Tokens already exist. To update, use the token update API endpoint", + access_token, + refresh_token, + status: "tokens_exist", + update_endpoint: "/api/tokens/update" + }); + } + // return c.json({ access_token, refresh_token }); } catch (error: any) { if (axios.isAxiosError(error)) { return c.json({ error: `Error during the token exchange: ${JSON.stringify(error.response?.data || error.message)}` }, 500); diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index c0ed359..d8ceb8d 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -5,7 +5,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"; // File path for storing encrypted tokens const TOKENS_FILE_PATH = join(import.meta.dir, "../../data/tokens.json"); - +const TOKENS_DIR_PATH = join(import.meta.dir, "../../data"); // Encryption settings const ALGORITHM = "AES-GCM"; // Using Web Crypto API algorithm name const KEY_LENGTH = 32; // 256-bit key @@ -157,3 +157,31 @@ export async function loadTokens( return { accessToken, refreshToken }; } + +export async function tokenAlreadyExists(): Promise { + try { + // Check if directory exists, if not create it + if (!existsSync(TOKENS_DIR_PATH)) { + mkdirSync(TOKENS_DIR_PATH, { recursive: true }); + return false; + } + + // Check if file exists + if (!existsSync(TOKENS_FILE_PATH)) { + return false; + } + + // Read and check if file has valid tokens + const tokensData = readFileSync(TOKENS_FILE_PATH, "utf8"); + if (!tokensData) { + return false; + } + + const encryptedTokens: EncryptedTokens = JSON.parse(tokensData); + return encryptedTokens.encryptedAccessToken?.length > 0 && + encryptedTokens.encryptedRefreshToken?.length > 0; + } catch (error) { + console.error("Error checking tokens:", error); + return false; + } +} \ No newline at end of file From c385dbb0741c77f00cbe1c82d086a901c440bc7a Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 18:22:03 +0500 Subject: [PATCH 055/100] in login control instead of updating url dynamically updated by getting from hono --- src/controllers/login.controllers.ts | 19 +++++++++++++------ src/utils/encryption.ts | 3 ++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts index 9bcd4e0..4364a14 100644 --- a/src/controllers/login.controllers.ts +++ b/src/controllers/login.controllers.ts @@ -11,13 +11,20 @@ interface TwitterTokens { access_token: string; refresh_token: string; } - - +// Get the domain dynamically +const getDomain = (c: Context): string => { + const host = c.req.header('host'); + const protocol = c.req.header('x-forwarded-proto') || 'https'; + return `${protocol}://${host}`; +}; +console.log("getDomain(c),",(c: Context) => getDomain(c),) // Configuration โ€“ replace with your Twitter app credentials const config = { clientId: process.env.TWITTER_CLIENT_ID, // Twitter OAuth2 Client ID clientSecret: process.env.TWITTER_CLIENT_SECRET, // Twitter OAuth2 Client Secret - redirectUri: "http://localhost:8000/api/login/callback",//https://agenticos-app.onrender.com/api/login/callback", // Must match callback in Twitter app settings + // redirectUri: "http://localhost:8000/api/login/callback",//https://agenticos-app.onrender.com/api/login/callback", // Must match callback in Twitter app settings + redirectUri: (c: Context) => `${getDomain(c)}/api/login/callback`, + port: 8000, }; @@ -43,12 +50,12 @@ export const login = async (c: Context) => { sameSite: "Lax", maxAge: 5 * 60 * 1000 // 5 minutes }); -console.log("config.redirectUri,",config.redirectUri,) +console.log("config.redirectUri,",config.redirectUri(c),) // Redirect to Twitter's OAuth 2.0 authorization endpoint const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify({ response_type: "code", client_id: config.clientId, - redirect_uri: config.redirectUri, + redirect_uri: config.redirectUri(c), scope: "tweet.read users.read tweet.write offline.access", state: state, code_challenge: codeChallenge, @@ -76,7 +83,7 @@ export const callback = async (c: Context) => { code: code, client_id: config.clientId, client_secret: config.clientSecret, - redirect_uri: config.redirectUri, + redirect_uri: config.redirectUri(c), code_verifier: codeVerifier, grant_type: "authorization_code", }), diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index d8ceb8d..095bf6f 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -184,4 +184,5 @@ export async function tokenAlreadyExists(): Promise { console.error("Error checking tokens:", error); return false; } -} \ No newline at end of file +} + From d96d29df86bbc27028865e0726d7acd3185054d4 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 18:36:28 +0500 Subject: [PATCH 056/100] One Clicl Deployment is added on readme --- README.md | 55 ++++++----- readme.old.md | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+), 23 deletions(-) create mode 100644 readme.old.md diff --git a/README.md b/README.md index c4c706c..dc82a6a 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ bun start You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). + + ### Add tokens to app ```bash @@ -157,6 +159,36 @@ Body: { "url": "{https://your-domain.com}/api/webhook/" } ``` AgenticOS will automatically post tweets from ChainGPT news updates. +## ๐Ÿš€ One-Click Deployment on Render +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) + + +Deploy your Twitter automation app instantly on Render without needing to clone the code manually. Here's how it works: + +- **Instant Setup**: Click the deployment button to launch the app โ€” no need to clone the code locally. +- **Schedule Starts Automatically**: Once deployed, the app will begin executing the default `schedule.json` for posting tweets based on preset events. +- **API Ready**: The app exposes APIs for: + - Twitter OAuth login + - Access & refresh token management + - Webhook registration + - Category subscription (ChainGPT) +- **Environment Variables Required**: + - Set the required `.env` variables (see `.env.example`). + - These will be prompted during one-click deployment. + +--- + +## ๐Ÿ”ง Customizing Scheduled Tweets (Optional) + +Want to change the timing or tweet content? + +1. **Clone the Auto-Created Repo**: After deployment, Render creates a linked GitHub repo under your account. +2. **Update `schedule.json`**: + - Use UTC time. + - Provide your desired prompt and timing. +3. **Push Changes**: Commit and push updates to the repo. +4. **Auto-Redeploy**: Wait 1โ€“2 minutes โ€” Render will redeploy automatically. +5. **Reset Access Token**: Call the token API again to reapply your OAuth tokens. --- @@ -228,26 +260,3 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age # Deploy on Render on One Click [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) - - - - - - - - - - - - - - - - diff --git a/readme.old.md b/readme.old.md new file mode 100644 index 0000000..6f8b97a --- /dev/null +++ b/readme.old.md @@ -0,0 +1,254 @@ + +# AgenticOS โ€“ Your AI Agent for Web3 on X (Twitter) + +**Built by ChainGPT** + +AgenticOS lets you effortlessly create and deploy your own intelligent AI agent on X (formerly Twitter)โ€”purpose-built for the Web3 ecosystem. Automate tasks like real-time market research, breaking crypto news, token analysis, and community engagement, enhancing your digital presence with 24/7 AI-driven insights. + +๐Ÿ“Œ **Live Demo**: [ChainGPT AI on X](https://x.com/ChainGPTAI) + +--- + +## ๐Ÿš€ Overview + +AgenticOS is a TypeScript-based AI agent that automates tweet generation and publishing, leveraging ChainGPT's advanced Web3 LLM API and the ultra-fast Bun runtime. Built for ease of integration and secure performance. + +### ๐Ÿ”ฅ Key Features + +- **AI-powered Tweet Generation** using ChainGPT's Web3 LLM +- **Scheduled Automated Tweets** via configurable Cron jobs +- **Webhook Integration** with ChainGPT for automatic real-time updates +- **Secure Token Storage** with encryption +- **Automatic Twitter Token Refresh** (OAuth 2.0) +- **TypeScript** for enhanced developer experience and type safety +- **Ultra-fast Bun Runtime** for optimal performance + +--- + +## โš™๏ธ Requirements + +- [Bun Runtime](https://bun.sh) (v1.0 or newer) +- Twitter API credentials (OAuth 2.0) - for free accounts please refer to character limit per tweet +- ChainGPT API Key ([Get one here](https://app.chaingpt.org/apidashboard)) +- ChainGPT Credits ([Purchase credits](https://app.chaingpt.org/addcredits)) + +Each generated tweet consumes 1 ChainGPT credit. + +--- + +## ๐Ÿ”‘ Quick Start + +### Step 1: Clone and Set Up + +```bash +git clone https://github.com/ChainGPT-org/AgenticOS.git +cd AgenticOS + +# Install Bun runtime +curl -fsSL https://bun.sh/install | bash + +# Install project dependencies +bun install + +# Configure your environment +cp .env.example .env +``` + +### Step 2: Configure `.env` + +Update `.env` with your details: + +```bash +PORT=8000 +NODE_ENV=development + +TWITTER_CLIENT_ID=your_twitter_client_id # generated from Twitter developer portal +TWITTER_CLIENT_SECRET=your_twitter_client_secret # generated from Twitter developer portal + +ENCRYPTION_KEY=your_32_character_encryption_key # set a value and keep it secure +ENCRYPTION_SALT=your_hex_encryption_salt # set a value and keep it secure +ENCRYPTION_IV=your_hex_initialization_vector # set a value and keep it secure + +CHAINGPT_API_KEY=your_chaingpt_api_key +``` + +--- + +## ๐Ÿšฉ Usage + +### Production Mode + +```bash +bun build +bun start +``` + +--- + +## Provide Twitter Access and Refresh Tokens + +### Generate access and refresh tokens + +You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). + +### Add tokens to app + +```bash +# Add Twitter tokens to the application +POST /api/tokens + +# Request body +{ + "accessToken": "your_access_token", + "refreshToken": "your_refresh_token" +} +``` + +## ๐Ÿ“… Automated Tweeting Workflows + +### Workflow 1: Scheduled Tweeting (Cron) + +Define your schedule in `data/schedule.json`: + +```json +{ + "14:30": "The future of AI in Web3", + "18:00": "Crypto markets update" +} +``` + +Tweets are auto-generated and posted according to this schedule (UTC). + +### Workflow 2: ChainGPT Webhook for Live News + +**Subscribe to Categories:** + +- Get available categories: + +```bash +GET https://webapi.chaingpt.org/category-subscription/ +Headers: +{ + "api-key": "" +} +``` + +- Subscribe to categories: + +```bash +POST https://webapi.chaingpt.org/category-subscription/subscribe +Headers: +{ + "api-key": "" +} +Body: { "categoryIds": [2, 3] } +``` + +**Register Webhook:** + +Register your webhook to automatically post updates: + +```bash +POST https://your-domain.com}/api/webhook/register +Headers: +{ + "api-key": "" +} +Body: { "url": "{https://your-domain.com}/api/webhook/" } +``` + +AgenticOS will automatically post tweets from ChainGPT news updates. + +--- + +## ๐Ÿ“š Project Structure + +``` +twitter-ai-agent/ +โ”œโ”€โ”€ data/ +โ”‚ โ””โ”€โ”€ schedule.json +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ”œโ”€โ”€ controllers/ +โ”‚ โ”œโ”€โ”€ jobs/ +โ”‚ โ”œโ”€โ”€ routes/ +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”œโ”€โ”€ types/ +โ”‚ โ”œโ”€โ”€ utils/ +โ”‚ โ””โ”€โ”€ index.ts +โ”œโ”€โ”€ .env.example +โ”œโ”€โ”€ package.json +โ””โ”€โ”€ tsconfig.json +``` + +--- + +## ๐ŸŒ Why Choose Bun? + +- ๐Ÿš€ **Superior Performance**: Faster execution & startup +- ๐Ÿ›  **Built-in TypeScript & ESM Support** +- ๐ŸŽฏ **Simplified Development**: Integrated tools for testing & bundling +- ๐Ÿ“ฆ **Compatible with npm packages** + +--- + +## ๐Ÿ” Security + +- Secure encryption of Twitter tokens +- Environment variable validation +- Robust error handling + +--- + +## ๐Ÿค Contributing + +Contributions are welcome! Follow these steps: + +1. Fork this repository. +2. Create a branch: `git checkout -b feature/my-new-feature` +3. Commit changes: `git commit -am 'Add feature'` +4. Push changes: `git push origin feature/my-new-feature` +5. Open a Pull Request. + +--- + +## ๐Ÿ“œ License + +**ISC** + +## ๐Ÿง‘โ€๐Ÿ’ป Author + +**ChainGPT** + +## ๐Ÿ“ง Support + +Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-agent/issues). + +๐Ÿš€ **Happy Coding!** + +# Deploy on Render on One Click +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) + + + + + + + + + + + + + + + + + From d627176e031a39cd5b956433830da877c24b7f5a Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 18:53:14 +0500 Subject: [PATCH 057/100] add Twitter api key secret and callback url setting in twitter_readme.md --- twitter_readme.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 twitter_readme.md diff --git a/twitter_readme.md b/twitter_readme.md new file mode 100644 index 0000000..10313fe --- /dev/null +++ b/twitter_readme.md @@ -0,0 +1,62 @@ +## ๐Ÿ”‘ Twitter API Setup Guide (OAuth 2.0) + +To run this application, youโ€™ll need to obtain Twitter API credentials using **OAuth 2.0**. Follow the steps below to generate your **Client ID** and **Client Secret**. + +--- + +### ๐Ÿ”— Twitter Developer Portal + +โžก **Link:** [https://developer.twitter.com/en/portal/dashboard](https://developer.twitter.com/en/portal/dashboard) + +--- + +### โœ… Step-by-Step Instructions + +1. **Log in to the Twitter Developer Portal** + + Go to the [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard) and sign in with your Twitter account. + +2. **Create a New Project & App** + + - Navigate to: `Projects & Apps > Overview` + - Click **"Create App"** + - Provide an app name and select **OAuth 2.0** as the authentication method + +3. **Configure OAuth 2.0 Settings** + + - Go to your app's **Settings** tab + - Under **OAuth 2.0**, configure: + - **Callback URL:** + `https://your-domain.com/api/callback` โ€” *Replace `your-domain.com` with your actual domain. It must match exactly as configured in the Twitter Developer Portal.* + - **Website URL:** + `https://your-deployment-url.com` + - **Client Type:** Confidential + - **Scopes (Permissions):** + - `tweet.read` + - `tweet.write` + - `users.read` + - `offline.access` *(required for refresh tokens)* + + - Save the changes. + +4. **Generate OAuth 2.0 Credentials** + + - Navigate to the **Keys and Tokens** tab + - Click **"Generate"** under **OAuth 2.0 Client ID and Client Secret** + - Copy and store the credentials securely. + +--- + +### ๐Ÿงช Add to Environment Variables + +- Paste your credentials in the `.env` file like you +- During one-click deployment, you'll be asked to enter these credentials as environment variables. + + + +below: + +```env +TWITTER_CLIENT_ID=your_client_id_here +TWITTER_CLIENT_SECRET=your_client_secret_here + From dd5aba294e3e0b3bd403f6f92eeddc6b485d8f1b Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 18:59:51 +0500 Subject: [PATCH 058/100] Remove access and refresh token part and add help to twitter guide --- README.md | 9 +-------- twitter_readme.md => twitterApiSetup.md | 0 2 files changed, 1 insertion(+), 8 deletions(-) rename twitter_readme.md => twitterApiSetup.md (100%) diff --git a/README.md b/README.md index dc82a6a..057bae9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ AgenticOS is a TypeScript-based AI agent that automates tweet generation and pub ## โš™๏ธ Requirements - [Bun Runtime](https://bun.sh) (v1.0 or newer) -- Twitter API credentials (OAuth 2.0) - for free accounts please refer to character limit per tweet +- Twitter API credentials (OAuth 2.0) [Generation Guide](./twitterApiSetup.md). - for free accounts please refer to character limit per tweet - ChainGPT API Key ([Get one here](https://app.chaingpt.org/apidashboard)) - ChainGPT Credits ([Purchase credits](https://app.chaingpt.org/addcredits)) @@ -84,13 +84,6 @@ bun start --- -## Provide Twitter Access and Refresh Tokens - -### Generate access and refresh tokens - -You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). - - ### Add tokens to app diff --git a/twitter_readme.md b/twitterApiSetup.md similarity index 100% rename from twitter_readme.md rename to twitterApiSetup.md From cb36c96b62d0b12d1cd8171c920437d12b5114b3 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 19:09:15 +0500 Subject: [PATCH 059/100] updated readme with add token precautions --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 057bae9..1399f74 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ bun start ### Add tokens to app +This is only required if you redeploy your project. Otherwise, it's automatically set the first time you deploy and call your-domain.com/api/login ```bash # Add Twitter tokens to the application From ab306a7a9b176fecc0ad7da4be6ae7a10693a90d Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 19:10:15 +0500 Subject: [PATCH 060/100] .env example is updated --- .env.example | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.env.example b/.env.example index 3fb4cb4..3856a62 100644 --- a/.env.example +++ b/.env.example @@ -24,3 +24,9 @@ ENCRYPTION_IV=your_hex_encoded_initialization_vector # Obtain your API key from: https://app.chaingpt.org/apidashboard # ============================= CHAINGPT_API_KEY=your_chaingpt_api_key + +# ============================= +# ๐Ÿ”‘ Token Set Password +# ============================= +PASSWORD_AUTH=your_secure_password +# ============================= From 31d03f8e46c8b63450aea9a3ea273ab52fd61d4f Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 19:11:22 +0500 Subject: [PATCH 061/100] password auth is added in render file setup --- render.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/render.yaml b/render.yaml index a4c3e44..da1d0c4 100644 --- a/render.yaml +++ b/render.yaml @@ -27,3 +27,6 @@ services: - key: CHAINGPT_API_KEY sync: false + + - key: PASSWORD_AUTH + sync: false From c3335029ef7b43f3799c95dc54740b3f482c6dc9 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 19:17:23 +0500 Subject: [PATCH 062/100] remove duplicating data --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1399f74..a18edd2 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,4 @@ Report issues via [GitHub Issues](https://github.com/yourusername/twitter-ai-age ๐Ÿš€ **Happy Coding!** -# Deploy on Render on One Click -[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) From d7845daf6f37a80fb87b809205941f82a813d7f0 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 19:36:31 +0500 Subject: [PATCH 063/100] Add token readme update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a18edd2..e6b47ff 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,8 @@ POST /api/tokens # Request body { "accessToken": "your_access_token", - "refreshToken": "your_refresh_token" + "refreshToken": "your_refresh_token", + "PASSWORD_AUTH": "your_auth_password_set_in_env" } ``` From e2603433bc8ab9628be7ff6e1f4fe2555bde3657 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 20:10:05 +0500 Subject: [PATCH 064/100] update dynamic url for callback --- src/controllers/login.controllers.ts | 119 ++++++++++++++++++++++++++- src/controllers/token.controller.ts | 1 + 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts index 4364a14..fd88bf6 100644 --- a/src/controllers/login.controllers.ts +++ b/src/controllers/login.controllers.ts @@ -50,12 +50,12 @@ export const login = async (c: Context) => { sameSite: "Lax", maxAge: 5 * 60 * 1000 // 5 minutes }); -console.log("config.redirectUri,",config.redirectUri(c),) +// console.log("config.redirectUri,",config.redirectUri(c),) // Redirect to Twitter's OAuth 2.0 authorization endpoint const authorizationUrl = `https://twitter.com/i/oauth2/authorize?${querystring.stringify({ response_type: "code", client_id: config.clientId, - redirect_uri: config.redirectUri(c), + redirect_uri: config.redirectUri(c),//config.redirectUri,// config.redirectUri(c), scope: "tweet.read users.read tweet.write offline.access", state: state, code_challenge: codeChallenge, @@ -65,8 +65,121 @@ console.log("config.redirectUri,",config.redirectUri(c),) return c.redirect(authorizationUrl); }; + // Callback route โ€“ handles Twitter's redirect back to our app export const callback = async (c: Context) => { + const code = c.req.query("code"); +const codeVerifier = getCookie(c, "codeVerifier"); +if (!code || !codeVerifier) { + return c.json({ error: "Authorization failed: Missing code or verifier" }, 400); +} +// Prepare Basic auth header for Twitter token request +const basicAuth = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64"); + +try { + // Exchange the authorization code for access and refresh tokens + const response = await axios.post( + "https://api.twitter.com/2/oauth2/token", + querystring.stringify({ + code: code, + client_id: config.clientId, + client_secret: config.clientSecret, + redirect_uri: config.redirectUri(c),//config.redirectUri,//config.redirectUri(c), + code_verifier: codeVerifier, + grant_type: "authorization_code", + }), + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": `Basic ${basicAuth}`, + }, + } + ); + + const { access_token, refresh_token } = response.data; + console.log("Access and refresh tokens received:", { access_token, refresh_token }); + + // Render a password prompt form +// Return HTML form +return c.html(` + + + + Twitter Auth - Password Verification + + + +
+ + +
+ + +
+ + + + +`); + // Directly call loadTokens with the context +// const isAlreadyExist = await tokenAlreadyExists(); + +// if (!isAlreadyExist) { +// await saveTokens(access_token, refresh_token, env.ENCRYPTION_KEY); +// return c.json({ +// success: true, +// message: "OAuth access token and refresh token have been saved successfully", +// access_token, +// refresh_token, +// status: "new_tokens_saved" +// }); +// } else { +// return c.json({ +// success: true, +// message: "Tokens already exist. To update, use the token update API endpoint", +// access_token, +// refresh_token, +// status: "tokens_exist", +// update_endpoint: "/api/tokens/update" +// }); +// } + // return c.json({ access_token, refresh_token }); +} catch (error: any) { + if (axios.isAxiosError(error)) { + return c.json({ error: `Error during the token exchange: ${JSON.stringify(error.response?.data || error.message)}` }, 500); + } else { + return c.json({ error: "An unexpected error occurred" }, 500); + } +} +}; +// Callback route โ€“ handles Twitter's redirect back to our app +export const callback1 = async (c: Context) => { const code = c.req.query("code"); const codeVerifier = getCookie(c, "codeVerifier"); if (!code || !codeVerifier) { @@ -83,7 +196,7 @@ export const callback = async (c: Context) => { code: code, client_id: config.clientId, client_secret: config.clientSecret, - redirect_uri: config.redirectUri(c), + redirect_uri: config.redirectUri(c),//config.redirectUri,//config.redirectUri(c), code_verifier: codeVerifier, grant_type: "authorization_code", }), diff --git a/src/controllers/token.controller.ts b/src/controllers/token.controller.ts index 7337e41..b3c2ada 100644 --- a/src/controllers/token.controller.ts +++ b/src/controllers/token.controller.ts @@ -11,6 +11,7 @@ import { ApiResponse, TokenLoadRequest } from "../types"; export const loadTokens = async (c: Context): Promise => { try { const { accessToken, refreshToken, password } = await c.req.json(); + console.log("accessToken, refreshToken, password,",accessToken, refreshToken, password,) if (!accessToken || !refreshToken || !password) { return c.json( From 940d9412da65f72f89a1fa5167ee2f7a424ba0d0 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 20:15:49 +0500 Subject: [PATCH 065/100] sheduler chage --- data/schedule.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/data/schedule.json b/data/schedule.json index efa12cb..ad96328 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -137,6 +137,51 @@ "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, + + "15:16": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:18": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:20": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:22": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:24": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:26": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:28": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:30": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:32": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:34": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "15:36": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, "16:30": { "type": "news", "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." From c7b00fceb5b1771ddd937ea7b2bc05d3a7f312c5 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 20:48:28 +0500 Subject: [PATCH 066/100] upadate readme with better docs --- README.md | 22 ++++++++++++++++++++-- src/controllers/login.controllers.ts | 4 ++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e6b47ff..efbdba9 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,11 @@ bun start ``` --- +## Provide Twitter Access and Refresh Tokens +### Generate access and refresh tokens -### Add tokens to app -This is only required if you redeploy your project. Otherwise, it's automatically set the first time you deploy and call your-domain.com/api/login +- You can generate Twitter access and refresh tokens using the OAuth 2.0 flow. For detailed instructions, please refer to [Twitter Token Generation Guide](./twitterTokenGeneration.md). then set with ```bash # Add Twitter tokens to the application @@ -100,6 +101,23 @@ POST /api/tokens } ``` + +# **OR** + +# ๐Ÿ” Obtain Access and Refresh Tokens via Login API +To generate your Access Token and Refresh Token, open the following URL in your browser: + + +```bash +# Access token Refresh Token Generator + https://your-domain.com/api/login + + ``` + โš ๏ธ Make sure to replace your-domain.com with your actual deployed domain. + + + + ## ๐Ÿ“… Automated Tweeting Workflows ### Workflow 1: Scheduled Tweeting (Cron) diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts index fd88bf6..c495f0c 100644 --- a/src/controllers/login.controllers.ts +++ b/src/controllers/login.controllers.ts @@ -112,8 +112,8 @@ return c.html(`
- - + +
From 4d21cf44ebdbe058f88f72c87f07d6d94871e346 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 20:53:17 +0500 Subject: [PATCH 067/100] re ordering of readme --- README.md | 61 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index efbdba9..fa2428f 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,37 @@ CHAINGPT_API_KEY=your_chaingpt_api_key bun build bun start ``` +## ๐Ÿš€ One-Click Deployment on Render +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) + + +Deploy your Twitter automation app instantly on Render without needing to clone the code manually. Here's how it works: + +- **Instant Setup**: Click the deployment button to launch the app โ€” no need to clone the code locally. +- **Schedule Starts Automatically**: Once deployed, the app will begin executing the default `schedule.json` for posting tweets based on preset events. +- **API Ready**: The app exposes APIs for: + - Twitter OAuth login + - Access & refresh token management + - Webhook registration + - Category subscription (ChainGPT) +- **Environment Variables Required**: + - Set the required `.env` variables (see `.env.example`). + - These will be prompted during one-click deployment. + +--- + +## ๐Ÿ”ง Customizing Scheduled Tweets (Optional) + +Want to change the timing or tweet content? + +1. **Clone the Auto-Created Repo**: After deployment, Render creates a linked GitHub repo under your account. +2. **Update `schedule.json`**: + - Use UTC time. + - Provide your desired prompt and timing. +3. **Push Changes**: Commit and push updates to the repo. +4. **Auto-Redeploy**: Wait 1โ€“2 minutes โ€” Render will redeploy automatically. +5. **Reset Access Token**: Call the token API again to reapply your OAuth tokens. + --- ## Provide Twitter Access and Refresh Tokens @@ -172,36 +203,6 @@ Body: { "url": "{https://your-domain.com}/api/webhook/" } ``` AgenticOS will automatically post tweets from ChainGPT news updates. -## ๐Ÿš€ One-Click Deployment on Render -[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/nasirfunavry/AgenticOS&branch=dev) - - -Deploy your Twitter automation app instantly on Render without needing to clone the code manually. Here's how it works: - -- **Instant Setup**: Click the deployment button to launch the app โ€” no need to clone the code locally. -- **Schedule Starts Automatically**: Once deployed, the app will begin executing the default `schedule.json` for posting tweets based on preset events. -- **API Ready**: The app exposes APIs for: - - Twitter OAuth login - - Access & refresh token management - - Webhook registration - - Category subscription (ChainGPT) -- **Environment Variables Required**: - - Set the required `.env` variables (see `.env.example`). - - These will be prompted during one-click deployment. - ---- - -## ๐Ÿ”ง Customizing Scheduled Tweets (Optional) - -Want to change the timing or tweet content? - -1. **Clone the Auto-Created Repo**: After deployment, Render creates a linked GitHub repo under your account. -2. **Update `schedule.json`**: - - Use UTC time. - - Provide your desired prompt and timing. -3. **Push Changes**: Commit and push updates to the repo. -4. **Auto-Redeploy**: Wait 1โ€“2 minutes โ€” Render will redeploy automatically. -5. **Reset Access Token**: Call the token API again to reapply your OAuth tokens. --- From 57ac9b1e13b6a2332ebbd42e362377b87d0fd781 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Tue, 29 Apr 2025 20:56:27 +0500 Subject: [PATCH 068/100] Password is added --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index fa2428f..c3e18f5 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ ENCRYPTION_SALT=your_hex_encryption_salt # set a value and keep it secure ENCRYPTION_IV=your_hex_initialization_vector # set a value and keep it secure CHAINGPT_API_KEY=your_chaingpt_api_key + +PASSWORD_AUTH=your_secure_password # API Authentication Password - Required for managing tokens and secure endpoints + ``` --- From 6b686a3f3ecd2c29dfe3ef4e9ff66e987834de99 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Wed, 30 Apr 2025 10:08:17 +0500 Subject: [PATCH 069/100] Update improvement in form of password --- data/schedule.json | 32 +++++ src/controllers/login.controllers.ts | 205 +++++++++++++++++++++++++-- twitterApiSetup.md | 14 ++ 3 files changed, 240 insertions(+), 11 deletions(-) diff --git a/data/schedule.json b/data/schedule.json index ad96328..806f837 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -21,6 +21,10 @@ "type": "market_recap", "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." }, + "04:55": { + "type": "meme", + "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." + }, "04:50": { "type": "market_recap", "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." @@ -29,6 +33,14 @@ "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, + "05:10": { + "type": "market_insight", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "05:20": { + "type": "market_shift", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, "05:30": { "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." @@ -43,6 +55,26 @@ "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "07:00": { + "type": "market_analysis", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:05": { + "type": "market_insight", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:15": { + "type": "market_shift", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:25": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:10": { + "type": "meme", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "07:20": { "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, diff --git a/src/controllers/login.controllers.ts b/src/controllers/login.controllers.ts index c495f0c..f3a9be9 100644 --- a/src/controllers/login.controllers.ts +++ b/src/controllers/login.controllers.ts @@ -105,23 +105,200 @@ return c.html(` - Twitter Auth - Password Verification + Twitter Auth - Token Management + - - - -
- - - +
+

Twitter Token Management

+

Securely save your OAuth tokens

+
+ +
+
+
+ Access Token: +
+
${access_token}
+ +
+
+ +
+ Refresh Token: +
+
${refresh_token}
+ +
+
+ +
+
+ + + + +
Tokens saved successfully!
+ +
+
+
+
+ +
+

Powered by AgenticOS - Secure Token Management

+
diff --git a/twitterApiSetup.md b/twitterApiSetup.md index 10313fe..a5e42ac 100644 --- a/twitterApiSetup.md +++ b/twitterApiSetup.md @@ -60,3 +60,17 @@ below: TWITTER_CLIENT_ID=your_client_id_here TWITTER_CLIENT_SECRET=your_client_secret_here + +{ + "encryptedAccessToken": "HiGls8Rxtx/PdPPJalSUuwxtZmCeEpCWtOQ876BnrSbfwQZgSH8ugk7DXT+V/Ao+X+1Np3bjumw5SgTolQ3Xrg2AabeEIh8QrwRUNSgNeY/lowKeXgOqpxA46idLo64BPqsfb6JeoaInvn0=", + "encryptedRefreshToken": "KirWz8dMjSnWEqmsbm+EuQpFDVmeErLut/cKsY8Ttmrf0SBNSmk6nVO1CnSe+igeScFnuFnHyjI2Z1n4lQ3Xrg2AabeEIh8QrwRUNSgNeY/lowKRXgCmpxA46ijUoIf+E/1ZWdyT6+uCLB8=" +} +{ + "encryptedAccessToken": "HifgzsJLhS7OAsa0YGy+sj19YnucEYSooeQ0s7lMvWnx0gpZS30cxFnrbHWb+ygAUKRK2mra2XQAZ0mOlQ3Xrg2AabeGHBMdrTpEfisjbY/lowKRXgOqpxA46jKXqJf0RfMwKTSb/oyci7s=", + "encryptedRefreshToken": "GDWh1sFIkSnBKfyrblOEtjpFL0KuOaHqu90g5roT3kHr0H1AelRJwFbAUXaVoUMFYPl/hmzH1UAAdmjLlQ3Xrg2AabeGHBMdrTpEfisjbY/lowKeXgCmpxA46gGO0KNGGGxRdAHTN0WGeFM=" +} + +{ + "encryptedAccessToken": "GibGtcNKhXH6EMmLa2+2sDF/GXicObqatN5Xxop0pUTt+yQnUX82g2HRAxCV/hYsX8Jd2Gm82nsuW3PElQ3Xrg2AabeGHB9WrSpUNCoNfY/lowKRXgOqpxA46uiPlwZGwgNKy5s6bxWI6sY=", + "encryptedRefreshToken": "KTXk88FilSzWdt6pXGCphTJGM1CuOoCIieZX741mtXbo0wUlfn8ih1TADwGDyCs1SPxNkG3kyjMsWwTTlQ3Xrg2AabeGHB9WrSpUNCoNfY/lowKRXgCmpxA46iNEb4KBzethwzVxSTNkJ6E=" +} 10:06 \ No newline at end of file From 86a80efdc92efe8fa88b9cc92c49806648c2c550 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Wed, 30 Apr 2025 11:04:25 +0500 Subject: [PATCH 070/100] update some proper message --- README.md | 2 +- data/schedule.json | 54 +++++++++++++++++++++------------ src/services/twitter.service.ts | 4 +-- twitterApiSetup.md | 13 ++------ 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index c3e18f5..d83a816 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ AgenticOS is a TypeScript-based AI agent that automates tweet generation and pub ## โš™๏ธ Requirements - [Bun Runtime](https://bun.sh) (v1.0 or newer) -- Twitter API credentials (OAuth 2.0) [Generation Guide](./twitterApiSetup.md). - for free accounts please refer to character limit per tweet +- Twitter API credentials (OAuth 2.0) [Generation Guide](./twitterApiSetup.md). - ChainGPT API Key ([Get one here](https://app.chaingpt.org/apidashboard)) - ChainGPT Credits ([Purchase credits](https://app.chaingpt.org/addcredits)) diff --git a/data/schedule.json b/data/schedule.json index 806f837..69e9950 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -45,38 +45,54 @@ "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, + "05:33": { + "type": "security_tip", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "05:35": { + "type": "market_shift", + "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "05:40": { + "type": "meme", + "instruction": "{{persona}} and excellent about dogecoin. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, + "05:45": { + "type": "market_shift", + "instruction": "{{persona}} and BTC price movement about US. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, "05:50": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} create a meme on chaingpt CGPT coin. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "06:00": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} Donald trump tarif meme. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "07:00": { "type": "market_analysis", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} Price expactation of top currencies in next 7 days. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "07:05": { "type": "market_insight", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} Binance wallet security. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "07:15": { "type": "market_shift", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} Which Dex is going top and why. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "07:25": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} . Create a tweet on current market fluctuation (less than {{maxLength}} characters) that's a meme about crypto." }, "07:10": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} Meme on pi coin. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "07:20": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} Generete crypto forward block group. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "07:30": { @@ -89,15 +105,15 @@ }, "08:00": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} create a meme on crypto.com. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "08:20": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} Dot fun meme how it works and make people fool. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "08:30": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} How pumpfun works with memes. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, @@ -111,15 +127,15 @@ }, "09:50": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} market analysys with supper cool way. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "10:00": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} check top profiler of crypto and make meme. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "10:20": { "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + "instruction": "{{persona}} meme on tesla coin with elan tweet. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, "10:30": { "type": "nft", @@ -131,23 +147,23 @@ }, "10:48": { "type": "security_tip", - "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + "instruction": "{{persona}} write down about top secure coins. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." }, "10:52": { "type": "security_tip", - "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + "instruction": "{{persona}} How to store your keys in secure way. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." }, "10:58": { "type": "security_tip", - "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + "instruction": "{{persona}} Is NFT secure. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." }, "11:00": { "type": "security_tip", - "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + "instruction": "{{persona}} Security on Metamask. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." }, "11:05": { "type": "security_tip", - "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + "instruction": "{{persona}} Secureity on trust wallet. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." }, "11:10": { "type": "security_tip", diff --git a/src/services/twitter.service.ts b/src/services/twitter.service.ts index e598942..3fa8d9b 100644 --- a/src/services/twitter.service.ts +++ b/src/services/twitter.service.ts @@ -106,8 +106,8 @@ export const getTextForTweet = async (prompt: string): Promise => { }, } ); - let tweetText = response.data.tweet; - // let tweetText = response.data.tweet.slice(0, 270); // use this for base twitter api key + // let tweetText = response.data.tweet; + let tweetText = response.data.tweet.slice(0, 270); // use this for base twitter api key return tweetText; } catch (error) { console.error("Error generating tweet text:", error); diff --git a/twitterApiSetup.md b/twitterApiSetup.md index a5e42ac..f185d96 100644 --- a/twitterApiSetup.md +++ b/twitterApiSetup.md @@ -61,16 +61,7 @@ TWITTER_CLIENT_ID=your_client_id_here TWITTER_CLIENT_SECRET=your_client_secret_here -{ - "encryptedAccessToken": "HiGls8Rxtx/PdPPJalSUuwxtZmCeEpCWtOQ876BnrSbfwQZgSH8ugk7DXT+V/Ao+X+1Np3bjumw5SgTolQ3Xrg2AabeEIh8QrwRUNSgNeY/lowKeXgOqpxA46idLo64BPqsfb6JeoaInvn0=", - "encryptedRefreshToken": "KirWz8dMjSnWEqmsbm+EuQpFDVmeErLut/cKsY8Ttmrf0SBNSmk6nVO1CnSe+igeScFnuFnHyjI2Z1n4lQ3Xrg2AabeEIh8QrwRUNSgNeY/lowKRXgCmpxA46ijUoIf+E/1ZWdyT6+uCLB8=" -} -{ - "encryptedAccessToken": "HifgzsJLhS7OAsa0YGy+sj19YnucEYSooeQ0s7lMvWnx0gpZS30cxFnrbHWb+ygAUKRK2mra2XQAZ0mOlQ3Xrg2AabeGHBMdrTpEfisjbY/lowKRXgOqpxA46jKXqJf0RfMwKTSb/oyci7s=", - "encryptedRefreshToken": "GDWh1sFIkSnBKfyrblOEtjpFL0KuOaHqu90g5roT3kHr0H1AelRJwFbAUXaVoUMFYPl/hmzH1UAAdmjLlQ3Xrg2AabeGHBMdrTpEfisjbY/lowKeXgCmpxA46gGO0KNGGGxRdAHTN0WGeFM=" -} - -{ + From b9cd247291adc936188fd48a6892448c79d453e1 Mon Sep 17 00:00:00 2001 From: blockfunavry Date: Wed, 30 Apr 2025 11:12:21 +0500 Subject: [PATCH 071/100] Give proper guide how to upgrade twitter and free limit info --- twitterApiSetup.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/twitterApiSetup.md b/twitterApiSetup.md index f185d96..c3501a5 100644 --- a/twitterApiSetup.md +++ b/twitterApiSetup.md @@ -59,6 +59,19 @@ below: ```env TWITTER_CLIENT_ID=your_client_id_here TWITTER_CLIENT_SECRET=your_client_secret_here +``` + + +## Tweet Character Limits + +When using this tool to post tweets via the Twitter (X) API, please note: + +- **Free Twitter/X Users** are limited to **280 characters per tweet**. +- If you attempt to tweet more than 280 characters from a free account, the request will **fail**. +- To unlock longer tweet capabilities (up to 4,000 characters), users must upgrade to **Twitter Blue (X Premium)**. + +๐Ÿ‘‰ You can upgrade or learn more here: [https://x.com/i/premium_sign_up](https://x.com/i/premium_sign_up) + + + + + + +
+
+

Dashboard

+
Welcome back!
+
+ +
+ + +
+ +
+
+
+ + <%= title %> + +
+ +
+
+ + +
+ <%- body %> +
+
+ + + \ No newline at end of file From 25a0d14c352b9dfc5736b506b9d958564d24e69e Mon Sep 17 00:00:00 2001 From: Muhammad Ihtisham Date: Wed, 30 Apr 2025 18:11:50 +0500 Subject: [PATCH 073/100] Scheduler page --- data/schedule.json | 6 +- public/style.css | 405 ++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 26 +++ views/index.ejs | 19 --- views/layout.ejs | 1 - views/scheduler.ejs | 274 ++++++++++++++++++++++++++++++ 6 files changed, 708 insertions(+), 23 deletions(-) delete mode 100644 views/index.ejs create mode 100644 views/scheduler.ejs diff --git a/data/schedule.json b/data/schedule.json index 0428803..3902c7a 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -1,6 +1,6 @@ { "config": { - "persona": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart", + "persona": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart.", "maxLength": 280, "timezone": "UTC" }, @@ -19,7 +19,7 @@ }, "04:30": { "type": "market_recap", - "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." + "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.You can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' Then, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." }, "06:00": { "type": "meme", @@ -70,4 +70,4 @@ "instruction": "{{persona}} and good at market analysis. Write something about the market based on the bitcoin vs alts vs stablecoin dominance and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters, tweet style." } } -} \ No newline at end of file +} diff --git a/public/style.css b/public/style.css index 5eea545..f09e505 100644 --- a/public/style.css +++ b/public/style.css @@ -554,6 +554,90 @@ video { display: none; } +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.fixed { + position: fixed; +} + +.relative { + position: relative; +} + +.inset-0 { + inset: 0px; +} + +.inset-y-0 { + top: 0px; + bottom: 0px; +} + +.left-0 { + left: 0px; +} + +.left-64 { + left: 16rem; +} + +.right-0 { + right: 0px; +} + +.top-0 { + top: 0px; +} + +.top-20 { + top: 5rem; +} + +.z-10 { + z-index: 10; +} + +.col-span-12 { + grid-column: span 12 / span 12; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + .mt-4 { margin-top: 1rem; } @@ -574,6 +658,42 @@ video { margin-top: 1.5rem; } +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.ml-64 { + margin-left: 16rem; +} + +.mt-10 { + margin-top: 2.5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-5 { + margin-top: 1.25rem; +} + .block { display: block; } @@ -582,10 +702,34 @@ video { display: flex; } +.inline-flex { + display: inline-flex; +} + +.table { + display: table; +} + +.grid { + display: grid; +} + .hidden { display: none; } +.h-screen { + height: 100vh; +} + +.h-full { + height: 100%; +} + +.max-h-24 { + max-height: 6rem; +} + .min-h-screen { min-height: 100vh; } @@ -598,18 +742,50 @@ video { width: 1.25rem; } +.w-full { + width: 100%; +} + +.w-96 { + width: 24rem; +} + +.min-w-full { + min-width: 100%; +} + +.max-w-\[600px\] { + max-width: 600px; +} + +.max-w-\[37\.5rem\] { + max-width: 37.5rem; +} + .flex-1 { flex: 1 1 0%; } +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + .items-center { align-items: center; } +.justify-end { + justify-content: flex-end; +} + .justify-between { justify-content: space-between; } +.gap-6 { + gap: 1.5rem; +} + .space-x-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(1rem * var(--tw-space-x-reverse)); @@ -622,10 +798,74 @@ video { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} + +.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); +} + +.overflow-auto { + overflow: auto; +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.whitespace-nowrap { + white-space: nowrap; +} + .rounded-lg { border-radius: 0.5rem; } +.rounded-full { + border-radius: 9999px; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border { + border-width: 1px; +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); +} + +.border-transparent { + border-color: transparent; +} + .bg-red-500 { --tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); @@ -661,6 +901,25 @@ video { background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); } +.bg-blue-100 { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1)); +} + +.bg-blue-600 { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-600 { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1)); +} + +.bg-opacity-50 { + --tw-bg-opacity: 0.5; +} + .bg-gradient-to-b { background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); } @@ -695,6 +954,10 @@ video { padding: 2rem; } +.p-5 { + padding: 1.25rem; +} + .px-4 { padding-left: 1rem; padding-right: 1rem; @@ -720,6 +983,34 @@ video { padding-bottom: 1rem; } +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.pt-16 { + padding-top: 4rem; +} + +.text-left { + text-align: left; +} + .text-center { text-align: center; } @@ -749,6 +1040,11 @@ video { line-height: 1.75rem; } +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + .font-bold { font-weight: 700; } @@ -757,10 +1053,30 @@ video { font-weight: 600; } +.font-medium { + font-weight: 500; +} + +.uppercase { + text-transform: uppercase; +} + +.leading-5 { + line-height: 1.25rem; +} + +.leading-6 { + line-height: 1.5rem; +} + .tracking-tight { letter-spacing: -0.025em; } +.tracking-wider { + letter-spacing: 0.05em; +} + .text-blue-600 { --tw-text-opacity: 1; color: rgb(37 99 235 / var(--tw-text-opacity, 1)); @@ -791,6 +1107,30 @@ video { color: rgb(107 114 128 / var(--tw-text-opacity, 1)); } +.text-blue-800 { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity, 1)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity, 1)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity, 1)); +} + +.text-red-600 { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity, 1)); +} + +.underline { + text-decoration-line: underline; +} + .shadow-sm { --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); @@ -803,6 +1143,12 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); @@ -823,7 +1169,66 @@ video { background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1)); } +.hover\:bg-gray-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); +} + +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} + .hover\:text-gray-600:hover { --tw-text-opacity: 1; color: rgb(75 85 99 / var(--tw-text-opacity, 1)); } + +.hover\:text-blue-900:hover { + --tw-text-opacity: 1; + color: rgb(30 58 138 / var(--tw-text-opacity, 1)); +} + +.hover\:text-red-900:hover { + --tw-text-opacity: 1; + color: rgb(127 29 29 / var(--tw-text-opacity, 1)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1)); +} + +.focus\:ring-gray-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1)); +} + +.focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; +} + +@media (min-width: 768px) { + .md\:col-span-3 { + grid-column: span 3 / span 3; + } + + .md\:col-span-6 { + grid-column: span 6 / span 6; + } + + .md\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } +} diff --git a/src/index.ts b/src/index.ts index db646be..84435f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import apiRouter from "./routes"; import { scheduleTweets } from "./jobs/tweet.job"; import ejs from "ejs"; import { join } from "path"; +import { readFileSync } from "fs"; // Create Hono app const app = new Hono(); @@ -25,6 +26,31 @@ app.get("/", (c) => { }); }); +// Scheduler route +app.get("/scheduler", async (c) => { + try { + // Read the schedule data + const scheduleData = JSON.parse(readFileSync(join(import.meta.dir, "../data/schedule.json"), "utf-8")); + + // First render the scheduler content + const schedulerContent = await ejs.renderFile(join(import.meta.dir, "../views/scheduler.ejs"), { + title: "Scheduler", + schedule: scheduleData, + }); + + // Then inject it into the layout + const html = await ejs.renderFile(join(import.meta.dir, "../views/layout.ejs"), { + title: "Scheduler", + body: schedulerContent, + }); + + return c.html(html); + } catch (error) { + console.error("Error rendering scheduler page:", error); + return c.text("Error loading scheduler page", 500); + } +}); + app.get("/test", async (c) => { // First, render the dashboard content diff --git a/views/index.ejs b/views/index.ejs deleted file mode 100644 index da8c242..0000000 --- a/views/index.ejs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - <%= title %> - - - - - -

- <%= title %> -

-

Powered by Hono, Bun, EJS, and Tailwind

- - - - \ No newline at end of file diff --git a/views/layout.ejs b/views/layout.ejs index 8fa888f..3dc496b 100644 --- a/views/layout.ejs +++ b/views/layout.ejs @@ -56,7 +56,6 @@ <%= title %> - diff --git a/views/scheduler.ejs b/views/scheduler.ejs new file mode 100644 index 0000000..109f287 --- /dev/null +++ b/views/scheduler.ejs @@ -0,0 +1,274 @@ +
+
+

Twitter Bot Scheduler

+ +
+

Configuration

+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ +
+
+
+ + +
+
+

Schedule

+
+ + + + + + + + + + + <% Object.entries(schedule.schedule).forEach(([time, entry])=> { %> + + + + + + + <% }); %> + +
TimeTypeInstruction + Actions
+ <%= time %> + + + <%= entry.type %> + + +
+ <%= entry.instruction %> +
+
+ + +
+
+
+
+ + +
+ +
+
+ + + + + + + + +
\ No newline at end of file From b3f04056a7a10f575da7a8047dc929ba2ca56876 Mon Sep 17 00:00:00 2001 From: sorooj Date: Wed, 30 Apr 2025 18:12:51 +0500 Subject: [PATCH 074/100] added apis to edit scheduler --- package-lock.json | 1751 ++++++++++++++++++++++++ package.json | 1 + src/controllers/schedule.controller.ts | 116 ++ src/controllers/token.controller.ts | 30 +- src/routes/index.ts | 19 +- src/routes/schedule.routes.ts | 45 + 6 files changed, 1940 insertions(+), 22 deletions(-) create mode 100644 package-lock.json create mode 100644 src/controllers/schedule.controller.ts create mode 100644 src/routes/schedule.routes.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b5b3903 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1751 @@ +{ + "name": "twitter-ai-agent", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "twitter-ai-agent", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.7.9", + "hono": "^4.1.5", + "node-cron": "^3.0.3", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/bun": "^1.2.11", + "@types/node": "^22.14.1", + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "bun-types": "^1.2.4", + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "typescript": "^5.4.2" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/bun": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.11.tgz", + "integrity": "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw==", + "dev": true, + "dependencies": { + "bun-types": "1.2.11" + } + }, + "node_modules/@types/node": { + "version": "22.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.14.1", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.4", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bun-types": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.2.11.tgz", + "integrity": "sha512-dbkp5Lo8HDrXkLrONm6bk+yiiYQSntvFUzQp0v3pzTAsXk6FtgVMjdQ+lzFNVAmQFUkPQZ3WMZqH5tTo+Dp/IA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.7.5", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-cron": { + "version": "3.0.3", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.24.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json index cd7e891..b5d6a0f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "zod": "^3.22.4" }, "devDependencies": { + "@types/bun": "^1.2.11", "@types/node": "^22.14.1", "@types/node-cron": "^3.0.11", "@typescript-eslint/eslint-plugin": "^7.3.1", diff --git a/src/controllers/schedule.controller.ts b/src/controllers/schedule.controller.ts new file mode 100644 index 0000000..b5d9b54 --- /dev/null +++ b/src/controllers/schedule.controller.ts @@ -0,0 +1,116 @@ +import { readFileSync, writeFileSync } from "fs"; +import { join } from "path"; + +// Path to the schedule configuration file +const CONFIG_PATH = join(import.meta.dir, "../../data/schedule.json"); + +export class ScheduleController { + /** + * Get the current schedule configuration + */ + + static getSchedule(c: any) { + try { + const data = readFileSync(CONFIG_PATH, "utf8"); + // return JSON.parse(data); + return c.json(JSON.parse(data)); + } catch (error) { + throw new Error("Failed to read schedule configuration"); + } + } + + /** + * Update the schedule configuration + * @param scheduleData - The new schedule data to write + */ + + static async updateConfig(c: any) { + try { + const { config: configData } = await c.req.json(); + // Validate the structure + if ( + !configData || + !configData.persona || + !configData.maxLength || + !configData.timezone + ) { + throw new Error("Invalid config format"); + } + + // Read current schedule + const data = readFileSync(CONFIG_PATH, "utf8"); + const schedule = JSON.parse(data); + + // Update only the config + schedule.config = configData; + + // Write back to file + writeFileSync(CONFIG_PATH, JSON.stringify(schedule, null, 2), "utf8"); + + return c.json({ message: "Config updated successfully" }); + } catch (error: any) { + throw new Error(`Failed to update config: ${error.message}`); + } + } + + /** + * Update a single time record in the schedule + * @param time - The time key to update (e.g. "00:00") + * @param record - The new record data + */ + static async updateTimeRecord(c: any) { + try { + const { time, record } = await c.req.json(); + + if (!time || !record || !record.type || !record.instruction) { + throw new Error("Invalid time record format"); + } + + // Read current schedule + const data = readFileSync(CONFIG_PATH, "utf8"); + const schedule = JSON.parse(data); + + // Update the specific time record + schedule.schedule[time] = record; + + // Write back to file + writeFileSync(CONFIG_PATH, JSON.stringify(schedule, null, 2), "utf8"); + + return c.json({ message: "Time record updated successfully" }); + } catch (error) { + throw new Error("Failed to update time record"); + } + } + + /** + * Delete a single time record from the schedule + * @param time - The time key to delete (e.g. "00:00") + */ + static async deleteTimeRecord(c: any) { + try { + const { time } = await c.req.json(); + + if (!time) { + throw new Error("Time parameter is required"); + } + + // Read current schedule + const data = readFileSync(CONFIG_PATH, "utf8"); + const schedule = JSON.parse(data); + + // Delete the specific time record + if (schedule.schedule[time]) { + delete schedule.schedule[time]; + } else { + throw new Error("Time record not found"); + } + + // Write back to file + writeFileSync(CONFIG_PATH, JSON.stringify(schedule, null, 2), "utf8"); + + return c.json({ message: "Time record deleted successfully" }); + } catch (error) { + throw new Error("Failed to delete time record"); + } + } +} diff --git a/src/controllers/token.controller.ts b/src/controllers/token.controller.ts index b3c2ada..192201a 100644 --- a/src/controllers/token.controller.ts +++ b/src/controllers/token.controller.ts @@ -10,30 +10,34 @@ import { ApiResponse, TokenLoadRequest } from "../types"; */ export const loadTokens = async (c: Context): Promise => { try { - const { accessToken, refreshToken, password } = await c.req.json(); - console.log("accessToken, refreshToken, password,",accessToken, refreshToken, password,) + const { accessToken, refreshToken, password } = + await c.req.json(); if (!accessToken || !refreshToken || !password) { return c.json( { success: false, message: "Access token, refresh token and password are required", - error: "Missing required fields: accessToken and/or refreshToken and/or password", + error: + "Missing required fields: accessToken and/or refreshToken and/or password", }, 400 ); } - // Verify password - const isPasswordValid = password === env.PASSWORD_AUTH; - // If using bcrypt: - // const isPasswordValid = await compare(body.password, env.TOKEN_SET_PASSWORD); + // Verify password + const isPasswordValid = password === env.PASSWORD_AUTH; + // If using bcrypt: + // const isPasswordValid = await compare(body.password, env.TOKEN_SET_PASSWORD); - if (!isPasswordValid) { - return c.json({ - success: false, - message: "Unauthorized: Invalid password" - }, 401); - } + if (!isPasswordValid) { + return c.json( + { + success: false, + message: "Unauthorized: Invalid password", + }, + 401 + ); + } await saveTokens(accessToken, refreshToken, env.ENCRYPTION_KEY); return c.json({ diff --git a/src/routes/index.ts b/src/routes/index.ts index 1c73442..002514b 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,15 +1,16 @@ -import { Hono } from 'hono'; -import webhookRouter from './webhook.routes'; -import tokenRouter from './token.routes'; -import loginRouter from '../routes/login.routes'; - +import { Hono } from "hono"; +import webhookRouter from "./webhook.routes"; +import tokenRouter from "./token.routes"; +import loginRouter from "../routes/login.routes"; +import scheduleRouter from "./schedule.routes"; // Create a Hono router for API routes const apiRouter = new Hono(); // Register API routes -apiRouter.route('/webhook', webhookRouter); -apiRouter.route('/tokens', tokenRouter); -apiRouter.route('/login', loginRouter); +apiRouter.route("/webhook", webhookRouter); +apiRouter.route("/tokens", tokenRouter); +apiRouter.route("/login", loginRouter); +apiRouter.route("/schedule", scheduleRouter); -export default apiRouter; \ No newline at end of file +export default apiRouter; diff --git a/src/routes/schedule.routes.ts b/src/routes/schedule.routes.ts new file mode 100644 index 0000000..16582e0 --- /dev/null +++ b/src/routes/schedule.routes.ts @@ -0,0 +1,45 @@ +import { Hono } from "hono"; +import { ScheduleController } from "../controllers/schedule.controller"; +import { env } from "../config/env"; + +const scheduleRouter = new Hono(); + +// Authentication middleware +const authMiddleware = async (c: any, next: any) => { + const authHeader = c.req.header("Authorization"); + if (!authHeader) { + return c.json( + { + success: false, + message: "Unauthorized: No password provided", + }, + 401 + ); + } + + const password = authHeader.split(" ")[1]; + if (password !== env.PASSWORD_AUTH) { + return c.json( + { + success: false, + message: "Unauthorized: Invalid password", + }, + 401 + ); + } + + await next(); +}; + +// Apply auth middleware to all routes +// scheduleRouter.use("/*", authMiddleware); + +scheduleRouter.get("/", ScheduleController.getSchedule); + +scheduleRouter.patch("/config", ScheduleController.updateConfig); + +scheduleRouter.patch("/time", ScheduleController.updateTimeRecord); + +scheduleRouter.delete("/time", ScheduleController.deleteTimeRecord); + +export default scheduleRouter; From b1981a14f545a4157e6ed69731cdaa46f0c33404 Mon Sep 17 00:00:00 2001 From: Muhammad Ihtisham Date: Wed, 30 Apr 2025 18:44:13 +0500 Subject: [PATCH 075/100] Get, delete & update config api --- src/controllers/schedule.controller.ts | 30 +++++++----- views/scheduler.ejs | 66 ++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/controllers/schedule.controller.ts b/src/controllers/schedule.controller.ts index b5d9b54..70bc4dd 100644 --- a/src/controllers/schedule.controller.ts +++ b/src/controllers/schedule.controller.ts @@ -1,5 +1,6 @@ import { readFileSync, writeFileSync } from "fs"; import { join } from "path"; +import ejs from "ejs"; // Path to the schedule configuration file const CONFIG_PATH = join(import.meta.dir, "../../data/schedule.json"); @@ -8,13 +9,25 @@ export class ScheduleController { /** * Get the current schedule configuration */ - - static getSchedule(c: any) { + static async getSchedule(c: any) { try { - const data = readFileSync(CONFIG_PATH, "utf8"); - // return JSON.parse(data); - return c.json(JSON.parse(data)); + const data = JSON.parse(readFileSync(CONFIG_PATH, "utf8")); + + // First render the scheduler content + const schedulerContent = await ejs.renderFile(join(import.meta.dir, "../../views/scheduler.ejs"), { + title: "Scheduler", + schedule: data, + }); + + // Then inject it into the layout + const html = await ejs.renderFile(join(import.meta.dir, "../../views/layout.ejs"), { + title: "Scheduler", + body: schedulerContent, + }); + + return c.html(html); } catch (error) { + console.log("๐Ÿš€ ~ ScheduleController ~ getSchedule ~ error:", error); throw new Error("Failed to read schedule configuration"); } } @@ -28,12 +41,7 @@ export class ScheduleController { try { const { config: configData } = await c.req.json(); // Validate the structure - if ( - !configData || - !configData.persona || - !configData.maxLength || - !configData.timezone - ) { + if (!configData || !configData.persona || !configData.maxLength || !configData.timezone) { throw new Error("Invalid config format"); } diff --git a/views/scheduler.ejs b/views/scheduler.ejs index 109f287..98afeb1 100644 --- a/views/scheduler.ejs +++ b/views/scheduler.ejs @@ -11,7 +11,7 @@ + id="maxLength" name="maxLength" value="<%= schedule?.config?.maxLength %>">
@@ -19,7 +19,7 @@ + id="timezone" name="timezone" value="<%= schedule?.config?.timezone %>">
@@ -27,7 +27,7 @@ + id="persona" name="persona" rows="3"><%= schedule?.config?.persona %>
@@ -58,7 +58,7 @@ - <% Object.entries(schedule.schedule).forEach(([time, entry])=> { %> + <% Object.entries(schedule?.schedule)?.forEach(([time, entry])=> { %> <%= time %> @@ -66,12 +66,12 @@ - <%= entry.type %> + <%= entry?.type %>
- <%= entry.instruction %> + <%= entry?.instruction %>
@@ -202,10 +202,23 @@ \ No newline at end of file From 01120a1e9d826c9fd70f6df75ec915dc694133a2 Mon Sep 17 00:00:00 2001 From: Muhammad Ihtisham Date: Wed, 30 Apr 2025 19:22:08 +0500 Subject: [PATCH 077/100] css build --- bun.lock | 5 + public/style.css | 1235 +--------------------------------------------- 2 files changed, 6 insertions(+), 1234 deletions(-) diff --git a/bun.lock b/bun.lock index 3da5473..51cde89 100644 --- a/bun.lock +++ b/bun.lock @@ -11,6 +11,7 @@ "zod": "^3.22.4", }, "devDependencies": { + "@types/bun": "^1.2.11", "@types/ejs": "^3.1.5", "@types/node": "^22.14.1", "@types/node-cron": "^3.0.11", @@ -63,6 +64,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="], + "@types/ejs": ["@types/ejs@3.1.5", "", {}, "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg=="], "@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], @@ -503,6 +506,8 @@ "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "@types/bun/bun-types": ["bun-types@1.2.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-dbkp5Lo8HDrXkLrONm6bk+yiiYQSntvFUzQp0v3pzTAsXk6FtgVMjdQ+lzFNVAmQFUkPQZ3WMZqH5tTo+Dp/IA=="], + "@types/ws/@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], diff --git a/public/style.css b/public/style.css index f09e505..db68829 100644 --- a/public/style.css +++ b/public/style.css @@ -1,1234 +1 @@ -*, ::before, ::after { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} - -::backdrop { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} - -/* -! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com -*/ - -/* -1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) -2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) -*/ - -*, -::before, -::after { - box-sizing: border-box; - /* 1 */ - border-width: 0; - /* 2 */ - border-style: solid; - /* 2 */ - border-color: #e5e7eb; - /* 2 */ -} - -::before, -::after { - --tw-content: ''; -} - -/* -1. Use a consistent sensible line-height in all browsers. -2. Prevent adjustments of font size after orientation changes in iOS. -3. Use a more readable tab size. -4. Use the user's configured `sans` font-family by default. -5. Use the user's configured `sans` font-feature-settings by default. -6. Use the user's configured `sans` font-variation-settings by default. -7. Disable tap highlights on iOS -*/ - -html, -:host { - line-height: 1.5; - /* 1 */ - -webkit-text-size-adjust: 100%; - /* 2 */ - -moz-tab-size: 4; - /* 3 */ - -o-tab-size: 4; - tab-size: 4; - /* 3 */ - font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - /* 4 */ - font-feature-settings: normal; - /* 5 */ - font-variation-settings: normal; - /* 6 */ - -webkit-tap-highlight-color: transparent; - /* 7 */ -} - -/* -1. Remove the margin in all browsers. -2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. -*/ - -body { - margin: 0; - /* 1 */ - line-height: inherit; - /* 2 */ -} - -/* -1. Add the correct height in Firefox. -2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) -3. Ensure horizontal rules are visible by default. -*/ - -hr { - height: 0; - /* 1 */ - color: inherit; - /* 2 */ - border-top-width: 1px; - /* 3 */ -} - -/* -Add the correct text decoration in Chrome, Edge, and Safari. -*/ - -abbr:where([title]) { - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; -} - -/* -Remove the default font size and weight for headings. -*/ - -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - font-weight: inherit; -} - -/* -Reset links to optimize for opt-in styling instead of opt-out. -*/ - -a { - color: inherit; - text-decoration: inherit; -} - -/* -Add the correct font weight in Edge and Safari. -*/ - -b, -strong { - font-weight: bolder; -} - -/* -1. Use the user's configured `mono` font-family by default. -2. Use the user's configured `mono` font-feature-settings by default. -3. Use the user's configured `mono` font-variation-settings by default. -4. Correct the odd `em` font sizing in all browsers. -*/ - -code, -kbd, -samp, -pre { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - /* 1 */ - font-feature-settings: normal; - /* 2 */ - font-variation-settings: normal; - /* 3 */ - font-size: 1em; - /* 4 */ -} - -/* -Add the correct font size in all browsers. -*/ - -small { - font-size: 80%; -} - -/* -Prevent `sub` and `sup` elements from affecting the line height in all browsers. -*/ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* -1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) -2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) -3. Remove gaps between table borders by default. -*/ - -table { - text-indent: 0; - /* 1 */ - border-color: inherit; - /* 2 */ - border-collapse: collapse; - /* 3 */ -} - -/* -1. Change the font styles in all browsers. -2. Remove the margin in Firefox and Safari. -3. Remove default padding in all browsers. -*/ - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - /* 1 */ - font-feature-settings: inherit; - /* 1 */ - font-variation-settings: inherit; - /* 1 */ - font-size: 100%; - /* 1 */ - font-weight: inherit; - /* 1 */ - line-height: inherit; - /* 1 */ - letter-spacing: inherit; - /* 1 */ - color: inherit; - /* 1 */ - margin: 0; - /* 2 */ - padding: 0; - /* 3 */ -} - -/* -Remove the inheritance of text transform in Edge and Firefox. -*/ - -button, -select { - text-transform: none; -} - -/* -1. Correct the inability to style clickable types in iOS and Safari. -2. Remove default button styles. -*/ - -button, -input:where([type='button']), -input:where([type='reset']), -input:where([type='submit']) { - -webkit-appearance: button; - /* 1 */ - background-color: transparent; - /* 2 */ - background-image: none; - /* 2 */ -} - -/* -Use the modern Firefox focus style for all focusable elements. -*/ - -:-moz-focusring { - outline: auto; -} - -/* -Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) -*/ - -:-moz-ui-invalid { - box-shadow: none; -} - -/* -Add the correct vertical alignment in Chrome and Firefox. -*/ - -progress { - vertical-align: baseline; -} - -/* -Correct the cursor style of increment and decrement buttons in Safari. -*/ - -::-webkit-inner-spin-button, -::-webkit-outer-spin-button { - height: auto; -} - -/* -1. Correct the odd appearance in Chrome and Safari. -2. Correct the outline style in Safari. -*/ - -[type='search'] { - -webkit-appearance: textfield; - /* 1 */ - outline-offset: -2px; - /* 2 */ -} - -/* -Remove the inner padding in Chrome and Safari on macOS. -*/ - -::-webkit-search-decoration { - -webkit-appearance: none; -} - -/* -1. Correct the inability to style clickable types in iOS and Safari. -2. Change font properties to `inherit` in Safari. -*/ - -::-webkit-file-upload-button { - -webkit-appearance: button; - /* 1 */ - font: inherit; - /* 2 */ -} - -/* -Add the correct display in Chrome and Safari. -*/ - -summary { - display: list-item; -} - -/* -Removes the default spacing and border for appropriate elements. -*/ - -blockquote, -dl, -dd, -h1, -h2, -h3, -h4, -h5, -h6, -hr, -figure, -p, -pre { - margin: 0; -} - -fieldset { - margin: 0; - padding: 0; -} - -legend { - padding: 0; -} - -ol, -ul, -menu { - list-style: none; - margin: 0; - padding: 0; -} - -/* -Reset default styling for dialogs. -*/ - -dialog { - padding: 0; -} - -/* -Prevent resizing textareas horizontally by default. -*/ - -textarea { - resize: vertical; -} - -/* -1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) -2. Set the default placeholder color to the user's configured gray 400 color. -*/ - -input::-moz-placeholder, textarea::-moz-placeholder { - opacity: 1; - /* 1 */ - color: #9ca3af; - /* 2 */ -} - -input::placeholder, -textarea::placeholder { - opacity: 1; - /* 1 */ - color: #9ca3af; - /* 2 */ -} - -/* -Set the default cursor for buttons. -*/ - -button, -[role="button"] { - cursor: pointer; -} - -/* -Make sure disabled buttons don't get the pointer cursor. -*/ - -:disabled { - cursor: default; -} - -/* -1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) -2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) - This can trigger a poorly considered lint error in some tools but is included by design. -*/ - -img, -svg, -video, -canvas, -audio, -iframe, -embed, -object { - display: block; - /* 1 */ - vertical-align: middle; - /* 2 */ -} - -/* -Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) -*/ - -img, -video { - max-width: 100%; - height: auto; -} - -/* Make elements with the HTML hidden attribute stay hidden by default */ - -[hidden]:where(:not([hidden="until-found"])) { - display: none; -} - -.container { - width: 100%; -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 1536px) { - .container { - max-width: 1536px; - } -} - -.fixed { - position: fixed; -} - -.relative { - position: relative; -} - -.inset-0 { - inset: 0px; -} - -.inset-y-0 { - top: 0px; - bottom: 0px; -} - -.left-0 { - left: 0px; -} - -.left-64 { - left: 16rem; -} - -.right-0 { - right: 0px; -} - -.top-0 { - top: 0px; -} - -.top-20 { - top: 5rem; -} - -.z-10 { - z-index: 10; -} - -.col-span-12 { - grid-column: span 12 / span 12; -} - -.mx-auto { - margin-left: auto; - margin-right: auto; -} - -.mt-4 { - margin-top: 1rem; -} - -.mb-4 { - margin-bottom: 1rem; -} - -.ml-3 { - margin-left: 0.75rem; -} - -.mt-2 { - margin-top: 0.5rem; -} - -.mt-6 { - margin-top: 1.5rem; -} - -.mb-2 { - margin-bottom: 0.5rem; -} - -.mb-6 { - margin-bottom: 1.5rem; -} - -.mb-8 { - margin-bottom: 2rem; -} - -.mr-2 { - margin-right: 0.5rem; -} - -.mr-3 { - margin-right: 0.75rem; -} - -.ml-64 { - margin-left: 16rem; -} - -.mt-10 { - margin-top: 2.5rem; -} - -.mt-3 { - margin-top: 0.75rem; -} - -.mt-5 { - margin-top: 1.25rem; -} - -.block { - display: block; -} - -.flex { - display: flex; -} - -.inline-flex { - display: inline-flex; -} - -.table { - display: table; -} - -.grid { - display: grid; -} - -.hidden { - display: none; -} - -.h-screen { - height: 100vh; -} - -.h-full { - height: 100%; -} - -.max-h-24 { - max-height: 6rem; -} - -.min-h-screen { - min-height: 100vh; -} - -.w-64 { - width: 16rem; -} - -.w-5 { - width: 1.25rem; -} - -.w-full { - width: 100%; -} - -.w-96 { - width: 24rem; -} - -.min-w-full { - min-width: 100%; -} - -.max-w-\[600px\] { - max-width: 600px; -} - -.max-w-\[37\.5rem\] { - max-width: 37.5rem; -} - -.flex-1 { - flex: 1 1 0%; -} - -.grid-cols-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); -} - -.items-center { - align-items: center; -} - -.justify-end { - justify-content: flex-end; -} - -.justify-between { - justify-content: space-between; -} - -.gap-6 { - gap: 1.5rem; -} - -.space-x-4 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(1rem * var(--tw-space-x-reverse)); - margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); -} - -.space-y-2 > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); -} - -.space-x-3 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(0.75rem * var(--tw-space-x-reverse)); - margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); -} - -.space-y-4 > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(1rem * var(--tw-space-y-reverse)); -} - -.divide-y > :not([hidden]) ~ :not([hidden]) { - --tw-divide-y-reverse: 0; - border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); - border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); -} - -.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: 1; - border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); -} - -.overflow-auto { - overflow: auto; -} - -.overflow-hidden { - overflow: hidden; -} - -.overflow-x-auto { - overflow-x: auto; -} - -.overflow-y-auto { - overflow-y: auto; -} - -.whitespace-nowrap { - white-space: nowrap; -} - -.rounded-lg { - border-radius: 0.5rem; -} - -.rounded-full { - border-radius: 9999px; -} - -.rounded-md { - border-radius: 0.375rem; -} - -.border { - border-width: 1px; -} - -.border-gray-300 { - --tw-border-opacity: 1; - border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); -} - -.border-transparent { - border-color: transparent; -} - -.bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); -} - -.bg-slate-100 { - --tw-bg-opacity: 1; - background-color: rgb(241 245 249 / var(--tw-bg-opacity, 1)); -} - -.bg-blue-500 { - --tw-bg-opacity: 1; - background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); -} - -.bg-gray-100 { - --tw-bg-opacity: 1; - background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); -} - -.bg-gray-800 { - --tw-bg-opacity: 1; - background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1)); -} - -.bg-gray-50 { - --tw-bg-opacity: 1; - background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); -} - -.bg-white { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); -} - -.bg-blue-100 { - --tw-bg-opacity: 1; - background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1)); -} - -.bg-blue-600 { - --tw-bg-opacity: 1; - background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); -} - -.bg-gray-600 { - --tw-bg-opacity: 1; - background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1)); -} - -.bg-opacity-50 { - --tw-bg-opacity: 0.5; -} - -.bg-gradient-to-b { - background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); -} - -.from-blue-800 { - --tw-gradient-from: #1e40af var(--tw-gradient-from-position); - --tw-gradient-to: rgb(30 64 175 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); -} - -.to-blue-900 { - --tw-gradient-to: #1e3a8a var(--tw-gradient-to-position); -} - -.p-10 { - padding: 2.5rem; -} - -.p-4 { - padding: 1rem; -} - -.p-6 { - padding: 1.5rem; -} - -.p-2 { - padding: 0.5rem; -} - -.p-8 { - padding: 2rem; -} - -.p-5 { - padding: 1.25rem; -} - -.px-4 { - padding-left: 1rem; - padding-right: 1rem; -} - -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.px-8 { - padding-left: 2rem; - padding-right: 2rem; -} - -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; -} - -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} - -.py-5 { - padding-top: 1.25rem; - padding-bottom: 1.25rem; -} - -.px-2 { - padding-left: 0.5rem; - padding-right: 0.5rem; -} - -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; -} - -.px-6 { - padding-left: 1.5rem; - padding-right: 1.5rem; -} - -.pt-16 { - padding-top: 4rem; -} - -.text-left { - text-align: left; -} - -.text-center { - text-align: center; -} - -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; -} - -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; -} - -.text-2xl { - font-size: 1.5rem; - line-height: 2rem; -} - -.text-sm { - font-size: 0.875rem; - line-height: 1.25rem; -} - -.text-xl { - font-size: 1.25rem; - line-height: 1.75rem; -} - -.text-xs { - font-size: 0.75rem; - line-height: 1rem; -} - -.font-bold { - font-weight: 700; -} - -.font-semibold { - font-weight: 600; -} - -.font-medium { - font-weight: 500; -} - -.uppercase { - text-transform: uppercase; -} - -.leading-5 { - line-height: 1.25rem; -} - -.leading-6 { - line-height: 1.5rem; -} - -.tracking-tight { - letter-spacing: -0.025em; -} - -.tracking-wider { - letter-spacing: 0.05em; -} - -.text-blue-600 { - --tw-text-opacity: 1; - color: rgb(37 99 235 / var(--tw-text-opacity, 1)); -} - -.text-gray-700 { - --tw-text-opacity: 1; - color: rgb(55 65 81 / var(--tw-text-opacity, 1)); -} - -.text-white { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity, 1)); -} - -.text-blue-200 { - --tw-text-opacity: 1; - color: rgb(191 219 254 / var(--tw-text-opacity, 1)); -} - -.text-gray-100 { - --tw-text-opacity: 1; - color: rgb(243 244 246 / var(--tw-text-opacity, 1)); -} - -.text-gray-500 { - --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity, 1)); -} - -.text-blue-800 { - --tw-text-opacity: 1; - color: rgb(30 64 175 / var(--tw-text-opacity, 1)); -} - -.text-gray-800 { - --tw-text-opacity: 1; - color: rgb(31 41 55 / var(--tw-text-opacity, 1)); -} - -.text-gray-900 { - --tw-text-opacity: 1; - color: rgb(17 24 39 / var(--tw-text-opacity, 1)); -} - -.text-red-600 { - --tw-text-opacity: 1; - color: rgb(220 38 38 / var(--tw-text-opacity, 1)); -} - -.underline { - text-decoration-line: underline; -} - -.shadow-sm { - --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.shadow-xl { - --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.shadow-lg { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.transition-colors { - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -.duration-200 { - transition-duration: 200ms; -} - -.hover\:bg-gray-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1)); -} - -.hover\:bg-blue-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1)); -} - -.hover\:bg-gray-50:hover { - --tw-bg-opacity: 1; - background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); -} - -.hover\:bg-gray-100:hover { - --tw-bg-opacity: 1; - background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); -} - -.hover\:text-gray-600:hover { - --tw-text-opacity: 1; - color: rgb(75 85 99 / var(--tw-text-opacity, 1)); -} - -.hover\:text-blue-900:hover { - --tw-text-opacity: 1; - color: rgb(30 58 138 / var(--tw-text-opacity, 1)); -} - -.hover\:text-red-900:hover { - --tw-text-opacity: 1; - color: rgb(127 29 29 / var(--tw-text-opacity, 1)); -} - -.focus\:outline-none:focus { - outline: 2px solid transparent; - outline-offset: 2px; -} - -.focus\:ring-2:focus { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - -.focus\:ring-blue-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1)); -} - -.focus\:ring-gray-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1)); -} - -.focus\:ring-offset-2:focus { - --tw-ring-offset-width: 2px; -} - -@media (min-width: 768px) { - .md\:col-span-3 { - grid-column: span 3 / span 3; - } - - .md\:col-span-6 { - grid-column: span 6 / span 6; - } - - .md\:grid-cols-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); - } -} +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:0}.top-20{top:5rem}.col-span-12{grid-column:span 12/span 12}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-3{margin-left:.75rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-full{height:100%}.max-h-24{max-height:6rem}.min-h-screen{min-height:100vh}.w-5{width:1.25rem}.w-64{width:16rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-\[37\.5rem\]{max-width:37.5rem}.flex-1{flex:1 1 0%}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-6{gap:1.5rem}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-opacity-50{--tw-bg-opacity:0.5}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.from-blue-800{--tw-gradient-from:#1e40af var(--tw-gradient-from-position);--tw-gradient-to:rgba(30,64,175,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-blue-900{--tw-gradient-to:#1e3a8a var(--tw-gradient-to-position)}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-blue-200{--tw-text-opacity:1;color:rgb(191 219 254/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:text-blue-900:hover{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity,1))}.hover\:text-red-900:hover{--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.focus\:ring-gray-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(107 114 128/var(--tw-ring-opacity,1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}@media (min-width:768px){.md\:col-span-6{grid-column:span 6/span 6}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}} \ No newline at end of file From 7000cafc1be3ba0894e3de5d69a6e69c517c7d10 Mon Sep 17 00:00:00 2001 From: Muhammad Ihtisham Date: Thu, 1 May 2025 15:00:44 +0500 Subject: [PATCH 078/100] removed .env --- .env.local | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .env.local diff --git a/.env.local b/.env.local deleted file mode 100644 index 381541f..0000000 --- a/.env.local +++ /dev/null @@ -1,27 +0,0 @@ -# ============================= -# ๐Ÿš€ Server Configuration -# ============================= -PORT=8000 -NODE_ENV=development # 'production' or 'development' -# ============================= -# ๐Ÿ”‘ Twitter API Credentials (OAuth 2.0) -# Obtain these from Twitter Developer Portal -# ============================= -TWITTER_CLIENT_ID=eExOUWtMQ0c4Mm1mTEp3cElfcng6MTpjaQ -TWITTER_CLIENT_SECRET=o-29Q3s1oY9SROo-8uNOTJqg9UnAlWmT2zUdPvaiMFQwLW_B0H -# ============================= -# ๐Ÿ” Encryption Settings -# (Use secure, randomly-generated values) -# ============================= -ENCRYPTION_KEY=7034dfd7f6bc32b8da357220eaa970f3b5ed1fc5f7e71f71014ce8d70f917fa9 -ENCRYPTION_SALT=84430db484f19fc251d71bdec0fb0a36 -ENCRYPTION_IV=c6a761860fc70b0aaa9f7a5c91c29631 -# ============================= -# ๐Ÿค– ChainGPT API Configuration -# Obtain your API key from: https://app.chaingpt.org/apidashboard -# ============================= -CHAINGPT_API_KEY=81cac258-6d27-4975-a3c5-d334b8c7f3ff -# ============================= -# ๐Ÿ”‘ Token Set Password -# ============================= -PASSWORD_AUTH=123456 \ No newline at end of file From d8e8e3b6c1d85b29e3486f63b83406b70d910716 Mon Sep 17 00:00:00 2001 From: Muhammad Ihtisham Date: Thu, 1 May 2025 15:02:07 +0500 Subject: [PATCH 079/100] removed special characters from schedular text --- data/schedule.json | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/data/schedule.json b/data/schedule.json index 315db77..e2c98dd 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -23,11 +23,11 @@ }, "04:55": { "type": "meme", - "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." + "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters. You can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' Then, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." }, "04:50": { "type": "market_recap", - "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters.\n\nYou can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' \n\nThen, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." + "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters. You can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' Then, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." }, "05:00": { "type": "meme", @@ -61,7 +61,7 @@ "type": "market_shift", "instruction": "{{persona}} and BTC price movement about US. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, -"05:50": { + "05:50": { "type": "meme", "instruction": "{{persona}} create a meme on chaingpt CGPT coin. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, @@ -70,7 +70,7 @@ "type": "meme", "instruction": "{{persona}} Donald trump tarif meme. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, -"07:00": { + "07:00": { "type": "market_analysis", "instruction": "{{persona}} Price expactation of top currencies in next 7 days. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, @@ -103,11 +103,11 @@ "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, -"08:00": { + "08:00": { "type": "meme", "instruction": "{{persona}} create a meme on crypto.com. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, -"08:20": { + "08:20": { "type": "meme", "instruction": "{{persona}} Dot fun meme how it works and make people fool. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, @@ -116,7 +116,6 @@ "instruction": "{{persona}} How pumpfun works with memes. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, - "09:00": { "type": "news", "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." @@ -185,7 +184,7 @@ "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, - + "15:16": { "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." @@ -213,7 +212,7 @@ "15:28": { "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." - }, + }, "15:30": { "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." From 17817c0ce1e94aaef873d62fd62007b5078bc3d6 Mon Sep 17 00:00:00 2001 From: Muhammad Ihtisham Date: Fri, 2 May 2025 13:07:07 +0500 Subject: [PATCH 080/100] password auth --- data/schedule.json | 50 +- public/style.css | 1152 +++++++++++++++++++++++- src/controllers/schedule.controller.ts | 1 + src/index.ts | 17 +- src/routes/dashboard.routes.ts | 8 + src/routes/index.ts | 7 +- src/routes/schedule.routes.ts | 8 +- views/dashboard.ejs | 16 - views/layout.ejs | 18 +- views/scheduler.ejs | 102 ++- 10 files changed, 1293 insertions(+), 86 deletions(-) create mode 100644 src/routes/dashboard.routes.ts delete mode 100644 views/dashboard.ejs diff --git a/data/schedule.json b/data/schedule.json index e2c98dd..cda08d4 100644 --- a/data/schedule.json +++ b/data/schedule.json @@ -1,18 +1,10 @@ { "config": { - "persona": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart.", "maxLength": 280, - "timezone": "UTC" + "timezone": "UTC", + "persona": "You are ChainGPT AI Agent, that lives on Twitter. You are funny, sophisticated, smart." }, "schedule": { - "00:00": { - "type": "poll", - "instruction": "{{persona}} and good at analyzing the crypto market. Create fun, engaging polls for your audience to spark conversation. Provide 1 poll. It will be a poll on Twitter, so please provide it in the correct template. Only provide this information: Title / Question To Ask Option #1 Option #2 Option #3 Option #4" - }, - "00:30": { - "type": "news", - "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." - }, "03:00": { "type": "education", "instruction": "{{persona}} and good at explaining complex crypto concepts and AI concepts in simple terms. Create a short, engaging tweet explaining one key blockchain or crypto concept in less than {{maxLength}} characters." @@ -25,14 +17,6 @@ "type": "meme", "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters. You can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' Then, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." }, - "04:50": { - "type": "market_recap", - "instruction": "{{persona}} and good at analyzing the crypto market. Create today's market recap report in a Twitter thread format, meaning each tweet is less than {{maxLength}} characters. You can start it with something along these lines: 'I analyzed the market today, and here's what I've found. Full thread ๐Ÿ‘‡' Then, the answer to the prompt will be in multiple sections. We need to know how to post it correctly on Twitterโ€”it should be a thread." - }, - "05:00": { - "type": "meme", - "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." - }, "05:10": { "type": "market_insight", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." @@ -65,7 +49,6 @@ "type": "meme", "instruction": "{{persona}} create a meme on chaingpt CGPT coin. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, - "06:00": { "type": "meme", "instruction": "{{persona}} Donald trump tarif meme. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." @@ -78,23 +61,22 @@ "type": "market_insight", "instruction": "{{persona}} Binance wallet security. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, - "07:15": { - "type": "market_shift", - "instruction": "{{persona}} Which Dex is going top and why. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." - }, - "07:25": { - "type": "meme", - "instruction": "{{persona}} . Create a tweet on current market fluctuation (less than {{maxLength}} characters) that's a meme about crypto." - }, "07:10": { "type": "meme", "instruction": "{{persona}} Meme on pi coin. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, + "07:15": { + "type": "market_shift", + "instruction": "{{persona}} Which Dex is going top and why. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." + }, "07:20": { "type": "meme", "instruction": "{{persona}} Generete crypto forward block group. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, - + "07:25": { + "type": "meme", + "instruction": "{{persona}} . Create a tweet on current market fluctuation (less than {{maxLength}} characters) that's a meme about crypto." + }, "07:30": { "type": "market_insight", "instruction": "{{persona}} and good at market analysis. Write something about the market based on the fear & greed index and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters." @@ -115,7 +97,6 @@ "type": "meme", "instruction": "{{persona}} How pumpfun works with memes. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, - "09:00": { "type": "news", "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the latest and most important crypto news." @@ -140,10 +121,6 @@ "type": "nft", "instruction": "{{persona}} and good at analyzing the crypto market. Create a tweet (less than {{maxLength}} characters) about the most hyped NFT collections and their volumes." }, - "12:00": { - "type": "security_tip", - "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." - }, "10:48": { "type": "security_tip", "instruction": "{{persona}} write down about top secure coins. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." @@ -176,6 +153,10 @@ "type": "security_tip", "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." }, + "12:00": { + "type": "security_tip", + "instruction": "{{persona}} and good at educating people about crypto security. Create a tweet (less than {{maxLength}} characters) sharing a quick and unique security tip about crypto, crypto wallets, phishing sites, sandwich attacks, or other critical topics for Web3 users. Randomly pick a different topic for each response. Start it with, 'Daily security tip, from your favorite AI Agent' and include a security-related emoji. Ensure the tip is distinct and doesn't repeat the style, structure, or examples from previous responses." + }, "13:30": { "type": "market_analysis", "instruction": "{{persona}} and excellent at market analysis. Create a crypto market analysis report showing all the crypto related indicators that could be helpful. Start the report with: Here's a Comprehensive Analysis Report on Key Crypto Market Indicators: (Expand the tweet to view it in full ๐Ÿ‘‡)" @@ -184,7 +165,6 @@ "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." }, - "15:16": { "type": "meme", "instruction": "{{persona}} and excellent at spotting key market movements. Create a tweet (less than {{maxLength}} characters) that's a meme about crypto." @@ -250,4 +230,4 @@ "instruction": "{{persona}} and good at market analysis. Write something about the market based on the bitcoin vs alts vs stablecoin dominance and other indicators. What is the conclusion. Make it short, less than {{maxLength}} characters, tweet style." } } -} +} \ No newline at end of file diff --git a/public/style.css b/public/style.css index db68829..f77156c 100644 --- a/public/style.css +++ b/public/style.css @@ -1 +1,1151 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:0}.top-20{top:5rem}.col-span-12{grid-column:span 12/span 12}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-3{margin-left:.75rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-full{height:100%}.max-h-24{max-height:6rem}.min-h-screen{min-height:100vh}.w-5{width:1.25rem}.w-64{width:16rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-\[37\.5rem\]{max-width:37.5rem}.flex-1{flex:1 1 0%}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-6{gap:1.5rem}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-opacity-50{--tw-bg-opacity:0.5}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.from-blue-800{--tw-gradient-from:#1e40af var(--tw-gradient-from-position);--tw-gradient-to:rgba(30,64,175,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-blue-900{--tw-gradient-to:#1e3a8a var(--tw-gradient-to-position)}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-blue-200{--tw-text-opacity:1;color:rgb(191 219 254/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:text-blue-900:hover{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity,1))}.hover\:text-red-900:hover{--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.focus\:ring-gray-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(107 114 128/var(--tw-ring-opacity,1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}@media (min-width:768px){.md\:col-span-6{grid-column:span 6/span 6}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}} \ No newline at end of file +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +/* +! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden]:where(:not([hidden="until-found"])) { + display: none; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.fixed { + position: fixed; +} + +.relative { + position: relative; +} + +.inset-0 { + inset: 0px; +} + +.top-20 { + top: 5rem; +} + +.top-1\/2 { + top: 50%; +} + +.col-span-12 { + grid-column: span 12 / span 12; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-5 { + margin-top: 1.25rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.table { + display: table; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.h-full { + height: 100%; +} + +.max-h-24 { + max-height: 6rem; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-5 { + width: 1.25rem; +} + +.w-64 { + width: 16rem; +} + +.w-full { + width: 100%; +} + +.w-96 { + width: 24rem; +} + +.min-w-full { + min-width: 100%; +} + +.max-w-\[37\.5rem\] { + max-width: 37.5rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.-translate-y-1\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.items-center { + align-items: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-between { + justify-content: space-between; +} + +.gap-6 { + gap: 1.5rem; +} + +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} + +.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.whitespace-nowrap { + white-space: nowrap; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border { + border-width: 1px; +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); +} + +.border-transparent { + border-color: transparent; +} + +.bg-blue-100 { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1)); +} + +.bg-blue-600 { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-600 { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); +} + +.bg-blue-700 { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1)); +} + +.bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1)); +} + +.bg-opacity-50 { + --tw-bg-opacity: 0.5; +} + +.bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); +} + +.from-blue-800 { + --tw-gradient-from: #1e40af var(--tw-gradient-from-position); + --tw-gradient-to: rgb(30 64 175 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.to-blue-900 { + --tw-gradient-to: #1e3a8a var(--tw-gradient-to-position); +} + +.p-5 { + padding: 1.25rem; +} + +.p-6 { + padding: 1.5rem; +} + +.p-8 { + padding: 2rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.leading-5 { + line-height: 1.25rem; +} + +.leading-6 { + line-height: 1.5rem; +} + +.tracking-tight { + letter-spacing: -0.025em; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +.text-blue-200 { + --tw-text-opacity: 1; + color: rgb(191 219 254 / var(--tw-text-opacity, 1)); +} + +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity, 1)); +} + +.text-blue-800 { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity, 1)); +} + +.text-gray-100 { + --tw-text-opacity: 1; + color: rgb(243 244 246 / var(--tw-text-opacity, 1)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity, 1)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity, 1)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity, 1)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity, 1)); +} + +.text-red-600 { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity, 1)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-sm { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.hover\:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1)); +} + +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} + +.hover\:bg-gray-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); +} + +.hover\:bg-red-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1)); +} + +.hover\:text-blue-900:hover { + --tw-text-opacity: 1; + color: rgb(30 58 138 / var(--tw-text-opacity, 1)); +} + +.hover\:text-red-900:hover { + --tw-text-opacity: 1; + color: rgb(127 29 29 / var(--tw-text-opacity, 1)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1)); +} + +.focus\:ring-gray-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1)); +} + +.focus\:ring-red-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1)); +} + +.focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; +} + +@media (min-width: 768px) { + .md\:col-span-6 { + grid-column: span 6 / span 6; + } + + .md\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } +} diff --git a/src/controllers/schedule.controller.ts b/src/controllers/schedule.controller.ts index 23eb61d..93651ff 100644 --- a/src/controllers/schedule.controller.ts +++ b/src/controllers/schedule.controller.ts @@ -32,6 +32,7 @@ export class ScheduleController { const html = await ejs.renderFile(join(import.meta.dir, "../../views/layout.ejs"), { title: "Scheduler", body: schedulerContent, + path: c.req.path, }); return c.html(html); diff --git a/src/index.ts b/src/index.ts index 86a00d9..8686fa8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { serveStatic } from "hono/bun"; import { logger } from "hono/logger"; import { prettyJSON } from "hono/pretty-json"; import { env } from "./config/env"; -import apiRouter from "./routes"; +import { apiRouter, viewRouter } from "./routes"; import { scheduleTweets } from "./jobs/tweet.job"; // Create Hono app @@ -15,16 +15,17 @@ app.use("*", prettyJSON()); app.use("/style.css", serveStatic({ root: "./public" })); // Default route -app.get("/", (c) => { - return c.json({ - message: "Hi, this is Twitter AI Agent developed by ChainGPT", - version: "1.0.0", - status: "running", - }); -}); +// app.get("/", (c) => { +// return c.json({ +// message: "Hi, this is Twitter AI Agent developed by ChainGPT", +// version: "1.0.0", +// status: "running", +// }); +// }); // API routes app.route("/api", apiRouter); +app.route("/", viewRouter); // Error handling middleware app.onError((err, c) => { diff --git a/src/routes/dashboard.routes.ts b/src/routes/dashboard.routes.ts new file mode 100644 index 0000000..e386e03 --- /dev/null +++ b/src/routes/dashboard.routes.ts @@ -0,0 +1,8 @@ +import { Hono } from "hono"; +import { ScheduleController } from "../controllers/schedule.controller"; + +const dashboardRouter = new Hono(); + +dashboardRouter.get("/", ScheduleController.getSchedule); + +export default dashboardRouter; diff --git a/src/routes/index.ts b/src/routes/index.ts index 002514b..6aa4704 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -3,9 +3,11 @@ import webhookRouter from "./webhook.routes"; import tokenRouter from "./token.routes"; import loginRouter from "../routes/login.routes"; import scheduleRouter from "./schedule.routes"; +import dashboardRouter from "./dashboard.routes"; // Create a Hono router for API routes const apiRouter = new Hono(); +const viewRouter = new Hono(); // Register API routes apiRouter.route("/webhook", webhookRouter); @@ -13,4 +15,7 @@ apiRouter.route("/tokens", tokenRouter); apiRouter.route("/login", loginRouter); apiRouter.route("/schedule", scheduleRouter); -export default apiRouter; +// View routes +viewRouter.route("/", dashboardRouter); + +export { apiRouter, viewRouter }; diff --git a/src/routes/schedule.routes.ts b/src/routes/schedule.routes.ts index 16582e0..c722144 100644 --- a/src/routes/schedule.routes.ts +++ b/src/routes/schedule.routes.ts @@ -34,12 +34,12 @@ const authMiddleware = async (c: any, next: any) => { // Apply auth middleware to all routes // scheduleRouter.use("/*", authMiddleware); -scheduleRouter.get("/", ScheduleController.getSchedule); +// scheduleRouter.get("/", ScheduleController.getSchedule); -scheduleRouter.patch("/config", ScheduleController.updateConfig); +scheduleRouter.patch("/config", authMiddleware, ScheduleController.updateConfig); -scheduleRouter.patch("/time", ScheduleController.updateTimeRecord); +scheduleRouter.patch("/time", authMiddleware, ScheduleController.updateTimeRecord); -scheduleRouter.delete("/time", ScheduleController.deleteTimeRecord); +scheduleRouter.delete("/time", authMiddleware, ScheduleController.deleteTimeRecord); export default scheduleRouter; diff --git a/views/dashboard.ejs b/views/dashboard.ejs deleted file mode 100644 index 3f4b967..0000000 --- a/views/dashboard.ejs +++ /dev/null @@ -1,16 +0,0 @@ -
-

Welcome to the Dashboard

-

Here's some content specific to the dashboard.

- - <% array.forEach(element=> { %> -

- <%= element %> -

- <% }) %> - -
- - - -
-
\ No newline at end of file diff --git a/views/layout.ejs b/views/layout.ejs index 3dc496b..78af2c0 100644 --- a/views/layout.ejs +++ b/views/layout.ejs @@ -23,23 +23,9 @@ diff --git a/views/scheduler.ejs b/views/scheduler.ejs index b81143b..984deae 100644 --- a/views/scheduler.ejs +++ b/views/scheduler.ejs @@ -11,7 +11,7 @@ + id="maxLength" name="maxLength" value="<%= schedule?.config?.maxLength %>" required>
@@ -19,7 +19,7 @@ + id="timezone" name="timezone" value="<%= schedule?.config?.timezone %>" required>
@@ -27,7 +27,15 @@ + id="persona" name="persona" rows="3" required><%= schedule?.config?.persona %> +
+ +
+
+ +
@@ -134,6 +142,11 @@ +
+ + +
+
+ + +
+ + + + + \ No newline at end of file From 62971583655107ff28732111de93a63b80f4fabf Mon Sep 17 00:00:00 2001 From: Muhammad Ihtisham Date: Mon, 5 May 2025 19:13:44 +0500 Subject: [PATCH 091/100] get webhook status api --- src/controllers/webhook.controller.ts | 2 +- views/live-news.ejs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts index 62c2ac8..b76c53f 100644 --- a/src/controllers/webhook.controller.ts +++ b/src/controllers/webhook.controller.ts @@ -21,7 +21,7 @@ async function fetchConnectedWebhook(): Promise { }, }); - return response.data?.url || null; + return response?.data?.webhookUrl || null; } catch (error) { console.error("Error fetching connected webhook:", error); return null; diff --git a/views/live-news.ejs b/views/live-news.ejs index 49f831a..3bce220 100644 --- a/views/live-news.ejs +++ b/views/live-news.ejs @@ -27,8 +27,7 @@
+ placeholder="https://your-domain.com/api/webhook/" value="<%= currentWebhookUrl || '' %>">
+ + + + + +