diff --git a/README.md b/README.md index d6cd00e..f457108 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# twitter_v3 โ€” discord moderation bot +# twitter (v3) -a discord.js v14 bot base using traditional prefix commands (no slash commands) and structured winston logging. +a discord.js v14 moderation (+ more) bot --- diff --git a/db.json b/db.json new file mode 100644 index 0000000..83e6dd1 --- /dev/null +++ b/db.json @@ -0,0 +1,5 @@ +{ + "autoroles": { + "1362160460253761536": "1481824741361844244" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 7855c06..739ee88 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.js", "scripts": { "start": "node src/index.js", - "dev": "node --watch src/index.js" + "dev": "nodemon src/index.js" }, "repository": { "type": "git", @@ -22,9 +22,11 @@ "dependencies": { "discord.js": "^14.25.1", "dotenv": "^17.3.1", - "winston": "^3.19.0" + "nodemon": "^3.1.14", + "winston": "^3.19.0", + "winston-console-format": "^1.0.8" }, "overrides": { "undici": ">=6.23.0" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..7804fa3 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,672 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + discord.js: + specifier: ^14.25.1 + version: 14.25.1 + dotenv: + specifier: ^17.3.1 + version: 17.3.1 + nodemon: + specifier: ^3.1.14 + version: 3.1.14 + winston: + specifier: ^3.19.0 + version: 3.19.0 + winston-console-format: + specifier: ^1.0.8 + version: 1.0.8 + +packages: + + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@dabh/diagnostics@2.0.8': + resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + + '@discordjs/builders@1.13.1': + resolution: {integrity: sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@1.5.3': + resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@2.1.1': + resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} + engines: {node: '>=18'} + + '@discordjs/formatters@0.6.2': + resolution: {integrity: sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==} + engines: {node: '>=16.11.0'} + + '@discordjs/rest@2.6.0': + resolution: {integrity: sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==} + engines: {node: '>=18'} + + '@discordjs/util@1.2.0': + resolution: {integrity: sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==} + engines: {node: '>=18'} + + '@discordjs/ws@1.2.3': + resolution: {integrity: sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==} + engines: {node: '>=16.11.0'} + + '@sapphire/async-queue@1.5.5': + resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@sapphire/shapeshift@4.0.0': + resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==} + engines: {node: '>=v16'} + + '@sapphire/snowflake@3.5.3': + resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@so-ric/colorspace@1.1.6': + resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@vladfrangu/async_event_emitter@2.4.7': + resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} + + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} + + color@5.0.3: + resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} + engines: {node: '>=18'} + + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + discord-api-types@0.38.42: + resolution: {integrity: sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ==} + + discord.js@14.25.1: + resolution: {integrity: sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==} + engines: {node: '>=18'} + + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + + magic-bytes.js@1.13.0: + resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nodemon@3.1.14: + resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} + engines: {node: '>=10'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@6.21.3: + resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} + engines: {node: '>=18.17'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + winston-console-format@1.0.8: + resolution: {integrity: sha512-dq7t/E0D0QRi4XIOwu6HM1+5e//WPqylH88GVjKEhQVrzGFg34MCz+G7pMJcXFBen9C0kBsu5GYgbYsE2LDwKw==} + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} + engines: {node: '>= 12.0.0'} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + +snapshots: + + '@colors/colors@1.6.0': {} + + '@dabh/diagnostics@2.0.8': + dependencies: + '@so-ric/colorspace': 1.1.6 + enabled: 2.0.0 + kuler: 2.0.0 + + '@discordjs/builders@1.13.1': + dependencies: + '@discordjs/formatters': 0.6.2 + '@discordjs/util': 1.2.0 + '@sapphire/shapeshift': 4.0.0 + discord-api-types: 0.38.42 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.8.1 + + '@discordjs/collection@1.5.3': {} + + '@discordjs/collection@2.1.1': {} + + '@discordjs/formatters@0.6.2': + dependencies: + discord-api-types: 0.38.42 + + '@discordjs/rest@2.6.0': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/util': 1.2.0 + '@sapphire/async-queue': 1.5.5 + '@sapphire/snowflake': 3.5.3 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.42 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + + '@discordjs/util@1.2.0': + dependencies: + discord-api-types: 0.38.42 + + '@discordjs/ws@1.2.3': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/rest': 2.6.0 + '@discordjs/util': 1.2.0 + '@sapphire/async-queue': 1.5.5 + '@types/ws': 8.18.1 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.42 + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@sapphire/async-queue@1.5.5': {} + + '@sapphire/shapeshift@4.0.0': + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.23 + + '@sapphire/snowflake@3.5.3': {} + + '@so-ric/colorspace@1.1.6': + dependencies: + color: 5.0.3 + text-hex: 1.0.0 + + '@types/node@25.5.0': + dependencies: + undici-types: 7.18.2 + + '@types/triple-beam@1.3.5': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.5.0 + + '@vladfrangu/async_event_emitter@2.4.7': {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + async@3.2.6: {} + + balanced-match@4.0.4: {} + + binary-extensions@2.3.0: {} + + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + color-convert@3.1.3: + dependencies: + color-name: 2.1.0 + + color-name@2.1.0: {} + + color-string@2.1.4: + dependencies: + color-name: 2.1.0 + + color@5.0.3: + dependencies: + color-convert: 3.1.3 + color-string: 2.1.4 + + colors@1.4.0: {} + + debug@4.4.3(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + discord-api-types@0.38.42: {} + + discord.js@14.25.1: + dependencies: + '@discordjs/builders': 1.13.1 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.6.2 + '@discordjs/rest': 2.6.0 + '@discordjs/util': 1.2.0 + '@discordjs/ws': 1.2.3 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.38.42 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + dotenv@17.3.1: {} + + enabled@2.0.0: {} + + fast-deep-equal@3.1.3: {} + + fecha@4.2.3: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fn.name@1.1.0: {} + + fsevents@2.3.3: + optional: true + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + has-flag@3.0.0: {} + + ignore-by-default@1.0.1: {} + + inherits@2.0.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@2.0.1: {} + + kuler@2.0.0: {} + + lodash.snakecase@4.1.1: {} + + lodash@4.17.23: {} + + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + + magic-bytes.js@1.13.0: {} + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + + ms@2.1.3: {} + + nodemon@3.1.14: + dependencies: + chokidar: 3.6.0 + debug: 4.4.3(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 10.2.4 + pstree.remy: 1.1.8 + semver: 7.7.4 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + normalize-path@3.0.0: {} + + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + + picomatch@2.3.1: {} + + pstree.remy@1.1.8: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + semver@7.7.4: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.4 + + stack-trace@0.0.10: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + text-hex@1.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + touch@3.1.1: {} + + triple-beam@1.4.1: {} + + ts-mixer@6.0.4: {} + + tslib@2.8.1: {} + + undefsafe@2.0.5: {} + + undici-types@7.18.2: {} + + undici@6.21.3: {} + + util-deprecate@1.0.2: {} + + winston-console-format@1.0.8: + dependencies: + colors: 1.4.0 + logform: 2.7.0 + triple-beam: 1.4.1 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.19.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.8 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + + ws@8.19.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..d0eb205 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +ignoredBuiltDependencies: + - better-sqlite3 diff --git a/src/commands/autorole.js b/src/commands/autorole.js new file mode 100644 index 0000000..0046845 --- /dev/null +++ b/src/commands/autorole.js @@ -0,0 +1,84 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { time } = require("discord.js"); + +module.exports = { + name: 'autorole', + description: `assign a role to new members when they join the server`, + aliases: ['ar'], + usage: 'autorole ', + + async execute(client, message, args) { + if (!message.member.permissions.has('Administrator')) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> you do not have permission to use this command!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + + const role = message.mentions.roles.first() || message.guild.roles.cache.get(args[0]); + + if (!role) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> please provide a valid role!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + + // add the role to a json file for the guild and save it to be used when new members join (if the role is already in the database, update it to the new role) + const dbPath = path.join(__dirname, '..', '..', 'db.json'); + + let db = {}; + if (fs.existsSync(dbPath)) { + db = JSON.parse(fs.readFileSync(dbPath, 'utf8')); + } + + if (!db.autoroles) { + db.autoroles = {}; + } + + db.autoroles[message.guild.id] = role.id; + + fs.writeFileSync(dbPath, JSON.stringify(db, null, 2)); + + message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: `<:check:1481811973447356496> "${role.name}" has been set as the autorole!`, + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } +}; \ No newline at end of file diff --git a/src/commands/ban.js b/src/commands/ban.js new file mode 100644 index 0000000..8ba326c --- /dev/null +++ b/src/commands/ban.js @@ -0,0 +1,88 @@ +'use strict'; + +const { time } = require("discord.js"); + +module.exports = { + name: 'ban', + description: `ban a user from the current server`, + aliases: ['banish'], + usage: 'ban [reason] [duration]', + + async execute(client, message, args) { + if (!message.member.permissions.has('BanMembers')) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> you do not have permission to use this command!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + + const user = message.mentions.users.first() || client.users.cache.get(args[0]); + if (!user) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> please specify a user to ban!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + + const reason = args.slice(1).join(' ') || 'no reason provided (twitter)'; + const duration = args[2] ? parseInt(args[2]) : null; + + try { + await message.guild.members.ban(user, { reason }); + message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: `<:check:1481811973447356496> ${user.username} has been banned!`, + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } catch (error) { + console.error(error); + message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> an error occurred while trying to ban the user!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + }, +}; \ No newline at end of file diff --git a/src/commands/help.js b/src/commands/help.js index e617777..3100c68 100644 --- a/src/commands/help.js +++ b/src/commands/help.js @@ -1,8 +1,10 @@ +const { EmbedBuilder } = require('discord.js'); + 'use strict'; module.exports = { name: 'help', - description: 'Lists all available commands or shows info about a specific one.', + description: 'lists all available commands or shows info about a specific one', aliases: ['h', 'commands'], usage: 'help [command]', @@ -15,17 +17,29 @@ module.exports = { const command = client.commands.get(name); if (!command) { - return message.reply(`No command found for \`${name}\`.`); + return message.reply(`no command found for \`${name}\`.`); } - const lines = [`**${prefix}${command.name}**`]; - if (command.description) lines.push(`> ${command.description}`); - if (command.aliases?.length) - lines.push(`> **Aliases:** ${command.aliases.map((a) => `\`${prefix}${a}\``).join(', ')}`); - if (command.usage) - lines.push(`> **Usage:** \`${prefix}${command.usage}\``); + const embed = new EmbedBuilder() + .setColor(0x1da1f2) + .setTitle(`${prefix}${command.name}`) + .setDescription(command.description || 'no description'); - return message.reply(lines.join('\n')); + if (command.aliases?.length) { + embed.addFields({ + name: 'aliases', + value: command.aliases.map((a) => `\`${prefix}${a}\``).join(', '), + }); + } + + if (command.usage) { + embed.addFields({ + name: 'usage', + value: `\`${prefix}${command.usage}\``, + }); + } + + return message.reply({ embeds: [embed] }); } // Build a deduplicated command list (ignore aliases pointing to the same command) @@ -35,11 +49,27 @@ module.exports = { for (const command of client.commands.values()) { if (seen.has(command.name)) continue; seen.add(command.name); - commandList.push(`\`${prefix}${command.name}\` โ€” ${command.description || 'No description.'}`); + commandList.push(`\`${prefix}${command.name}\` โ€” ${command.description || 'no description'}`); } - return message.reply( - `**Available commands:**\n${commandList.join('\n')}\n\nUse \`${prefix}help \` for more details.`, - ); + const embed = { + color: 0x8aa2b3, + title: '', + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: `-# use ${prefix}help for more details`, + fields: [ + { name: 'commands', value: commandList.join('\n') }, + ], + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }; + + return message.reply({ embeds: [embed] }); }, }; diff --git a/src/commands/kick.js b/src/commands/kick.js new file mode 100644 index 0000000..9f0c18e --- /dev/null +++ b/src/commands/kick.js @@ -0,0 +1,88 @@ +'use strict'; + +const { time } = require("discord.js"); + +module.exports = { + name: 'kick', + description: `kick a user from the current server`, + aliases: ['kick'], + usage: 'kick [reason]', + + async execute(client, message, args) { + if (!message.member.permissions.has('KickMembers')) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> you do not have permission to use this command!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + + const user = message.mentions.users.first() || client.users.cache.get(args[0]); + if (!user) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> please specify a user to ban!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + + const reason = args.slice(1).join(' ') || 'no reason provided (twitter)'; + const duration = args[2] ? parseInt(args[2]) : null; + + try { + await message.guild.members.kick(user, { reason }); + message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: `<:check:1481811973447356496> ${user.username} has been kicked!`, + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } catch (error) { + console.error(error); + message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> an error occurred while trying to ban the user!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }); + } + }, +}; \ No newline at end of file diff --git a/src/commands/ping.js b/src/commands/ping.js index 64e0635..5567548 100644 --- a/src/commands/ping.js +++ b/src/commands/ping.js @@ -1,18 +1,43 @@ 'use strict'; +const { time } = require("discord.js"); + module.exports = { name: 'ping', - description: 'Replies with Pong! and shows the bot latency.', + description: `test the bot's latency to discord's servers and api`, aliases: ['pong'], usage: 'ping', async execute(client, message) { - const sent = await message.reply('Pingingโ€ฆ'); + const sent = await message.reply('pinging'); const latency = sent.createdTimestamp - message.createdTimestamp; const apiLatency = Math.round(client.ws.ping); - await sent.edit( - `๐Ÿ“ Pong!\n> Bot latency: **${latency}ms** | API latency: **${apiLatency}ms**`, - ); + const embed = { + color: 0x8aa2b3, + title: '', + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: 'fuck you!!!! ๐Ÿ–•', + fields: [ + { name: '# of cmds loaded', value: `**${client.commands.size}**`, inline: true }, + { name: 'bot latency', value: `**${latency}ms**`, inline: true }, + { name: 'api latency', value: `**${apiLatency}ms**`, inline: true }, + ], + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + thumbnail: { + url: client.user.displayAvatarURL(), + height: 1024, + width: 1024, + }, + }; + + await sent.edit({ content: null, embeds: [embed] }); }, }; diff --git a/src/commands/purge.js b/src/commands/purge.js new file mode 100644 index 0000000..66f6f1c --- /dev/null +++ b/src/commands/purge.js @@ -0,0 +1,61 @@ +'use strict'; + +const { time } = require("discord.js"); + +module.exports = { + name: 'purge', + description: `delete a specified number of messages from the current channel`, + aliases: ['clear', 'p'], + usage: 'purge ', + + async execute(client, message) { + if (!message.member.permissions.has('ManageMessages')) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + description: '<:no:1481811955592466492> you do not have permission to use this command!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }], + }); + } + + const args = message.content.split(' ').slice(1); + const amount = parseInt(args[0]) + 1; + + if (isNaN(amount) || amount < 1 || amount > 100) { + return message.reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + description: '<:no:1481811955592466492> please provide a number between 1 and 100!', + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }], + }); + } + + const deletedMessages = await message.channel.bulkDelete(amount, true); + const reply = await message.channel.send({ + content: null, embeds: [{ + color: 0x8aa2b3, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: `<:no:1481811955592466492> successfully deleted ${deletedMessages.size - 1} messages!`, + footer: { + text: `twitter @ ${message.guild.name}`, + icon_url: client.user.displayAvatarURL(), + }, + timestamp: new Date(), + }], + }); + setTimeout(() => reply.delete().catch(() => { }), 5000); + }, +}; diff --git a/src/events/guildMemberAdd.js b/src/events/guildMemberAdd.js new file mode 100644 index 0000000..5137a64 --- /dev/null +++ b/src/events/guildMemberAdd.js @@ -0,0 +1,29 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { Events } = require('discord.js'); +const logger = require('../utils/logger'); + +module.exports = (client, member) => { + const dbPath = path.join(__dirname, '..', '..', 'db.json'); + const db = JSON.parse(fs.readFileSync(dbPath, 'utf8')); + const roleId = db.autoroles ? db.autoroles[member.guild.id] : null; + + if (!roleId) { + logger.info(`member "${member.user.tag}" joined guild "${member.guild.name}", but no autorole is set up.`); + return; + } + + const role = member.guild.roles.cache.get(roleId); + + if (!role) { + logger.error(`role with id "${roleId}" not found in guild "${member.guild.name}".`); + return; + } + logger.info("member has joined the server! assigning member role..."); + + member.roles.add(role) + .then(() => logger.info(`assigned role "${role.name}" to user "${member.user.tag}"!`)) + .catch(error => logger.error(`failed to assign role "${role.name}" to user "${member.user.tag}": ${error}`)); +}; \ No newline at end of file diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index a5b1ff8..4b6a4be 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -21,16 +21,31 @@ module.exports = { if (!command) return; logger.info( - `[Command] "${commandName}" used by ${message.author.tag} (${message.author.id})` + - ` in ${message.guild ? `#${message.channel.name} (${message.guild.name})` : 'DMs'}`, + `[command] "${commandName}" used by ${message.author.tag} (${message.author.id})` + + ` in ${message.guild ? `#${message.channel.name} (${message.guild.name})` : 'dms'}`, ); try { await command.execute(client, message, args); } catch (err) { - logger.error(`[Command] Error executing "${commandName}":`, err); + logger.error(`[command] error executing "${commandName}":`, err); await message - .reply('An error occurred while running that command.') + .reply({ + content: null, embeds: [{ + color: 0x8aa2b3, + title: '', + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + description: '<:no:1481811955592466492> an error occurred while executing that command!', + footer: { + text: `${message.author.username} @ twitter`, + icon_url: message.author.displayAvatarURL(), + }, + timestamp: new Date(), + }] + }) .catch(() => null); } }, diff --git a/src/events/ready.js b/src/events/ready.js index 3279ccb..da448f9 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -1,5 +1,7 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); const { Events } = require('discord.js'); const logger = require('../utils/logger'); @@ -8,7 +10,19 @@ module.exports = { once: true, execute(client) { - logger.info(`[Ready] Logged in as ${client.user.tag} (${client.user.id})`); - logger.info(`[Ready] Serving ${client.guilds.cache.size} guild(s).`); + const db_path = path.join(process.cwd(), 'db.json'); + if (!fs.existsSync(db_path)) { + fs.writeFileSync(db_path, JSON.stringify({}, null, 2)); + logger.info('[db] created db.json'); + } + + const db = JSON.parse(fs.readFileSync(db_path, 'utf8')); + const variables = Object.keys(db); + logger.info(`[db] loaded variables from db.json: ${variables.length > 0 ? variables.join(', ') : 'none'}`); + + logger.info(`[ready] logged in as ${client.user.tag} (${client.user.id})`); + logger.info(`[ready] serving ${client.guilds.cache.size} guild(s)`); + + client.user.setActivity(`${client.guilds.cache.size} servers`, { type: 'WATCHING' }); }, }; diff --git a/src/handlers/commandHandler.js b/src/handlers/commandHandler.js index 0c54833..ef154e6 100644 --- a/src/handlers/commandHandler.js +++ b/src/handlers/commandHandler.js @@ -24,7 +24,7 @@ function loadCommands(client) { if (!command.name || typeof command.execute !== 'function') { logger.warn( - `[CommandHandler] Skipping ${file}: missing "name" or "execute".`, + `[commandhandler] skipping ${file}: missing "name" or "execute"`, ); continue; } @@ -38,14 +38,14 @@ function loadCommands(client) { } } - logger.debug(`[CommandHandler] Loaded command: ${command.name}`); + logger.debug(`[commandhandler] loaded command: ${command.name}`); loaded++; } catch (err) { - logger.error(`[CommandHandler] Failed to load ${file}:`, err); + logger.error(`[commandhandler] failed to load ${file}:`, err); } } - logger.info(`[CommandHandler] ${loaded} command(s) loaded.`); + logger.info(`[commandhandler] ${loaded} command(s) loaded`); } module.exports = { loadCommands }; diff --git a/src/handlers/eventHandler.js b/src/handlers/eventHandler.js index 958ceb0..f392042 100644 --- a/src/handlers/eventHandler.js +++ b/src/handlers/eventHandler.js @@ -2,6 +2,8 @@ const fs = require('fs'); const path = require('path'); +const { Events } = require('discord.js'); +const guildMemberAddEvent = require('../events/guildMemberAdd'); const logger = require('../utils/logger'); /** @@ -24,7 +26,7 @@ function loadEvents(client) { if (!event.name || typeof event.execute !== 'function') { logger.warn( - `[EventHandler] Skipping ${file}: missing "name" or "execute".`, + `[eventhandler] skipping ${file}: missing "name" or "execute"`, ); continue; } @@ -35,14 +37,17 @@ function loadEvents(client) { client.on(event.name, (...args) => event.execute(client, ...args)); } - logger.debug(`[EventHandler] Registered event: ${event.name}`); + logger.debug(`[eventhandler] registered event: ${event.name}`); loaded++; } catch (err) { - logger.error(`[EventHandler] Failed to load ${file}:`, err); + logger.error(`[eventhandler] failed to load ${file}:`, err); } } - logger.info(`[EventHandler] ${loaded} event(s) registered.`); + client.on(Events.GuildMemberAdd, guildMemberAddEvent.bind(null, client)); + loaded++; + + logger.info(`[eventhandler] ${loaded} event(s) registered`); } module.exports = { loadEvents }; diff --git a/src/index.js b/src/index.js index c536763..1036fea 100644 --- a/src/index.js +++ b/src/index.js @@ -19,6 +19,7 @@ if (!process.env.BOT_TOKEN) { const client = new Client({ intents: [ GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, // Required to read message content for prefix commands GatewayIntentBits.DirectMessages, @@ -34,16 +35,16 @@ loadEvents(client); // Global error handling process.on('unhandledRejection', (err) => { - logger.error('[Process] Unhandled rejection:', err); + logger.error('[process] unhandled rejection:', err); }); process.on('uncaughtException', (err) => { - logger.error('[Process] Uncaught exception:', err); + logger.error('[process] uncaught exception:', err); process.exit(1); }); // Connect to Discord client.login(process.env.BOT_TOKEN).catch((err) => { - logger.error('[Login] Failed to log in:', err); + logger.error('[login] failed to log in:', err); process.exit(1); }); diff --git a/src/utils/logger.js b/src/utils/logger.js index 669471d..663ef8f 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -1,45 +1,65 @@ 'use strict'; const { createLogger, format, transports } = require('winston'); -const { combine, timestamp, printf, colorize, errors } = format; +const { consoleFormat } = require("winston-console-format"); +const { printf } = format; const path = require('path'); const fs = require('fs'); -// Ensure logs directory exists -const logsDir = path.join(__dirname, '..', '..', 'logs'); -if (!fs.existsSync(logsDir)) { - fs.mkdirSync(logsDir, { recursive: true }); +const logs_dir = path.join(__dirname, '..', '..', 'logs'); +if (!fs.existsSync(logs_dir)) { + fs.mkdirSync(logs_dir, { recursive: true }); } -const logFormat = printf(({ level, message, timestamp: ts, stack }) => { - return `[${ts}] [${level}]: ${stack || message}`; -}); - const logger = createLogger({ - level: process.env.LOG_LEVEL || 'info', - format: combine( - timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - errors({ stack: true }), - logFormat, + level: "silly", + format: format.combine( + format.timestamp(), + format.ms(), + format.errors({ stack: true }), + format.splat(), + format.json() ), transports: [ - // Human-readable coloured output in the console new transports.Console({ - format: combine( - colorize({ all: true }), - timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - errors({ stack: true }), - logFormat, + format: format.combine( + format.colorize({ all: true }), + format.padLevels(), + consoleFormat({ + showMeta: true, + metaStrip: ["timestamp", "service"], + inspectOptions: { + depth: Infinity, + colors: true, + maxArrayLength: Infinity, + breakLength: 120, + compact: Infinity, + }, + }) ), }), - // All logs at info level and above written to combined.log + new transports.File({ - filename: path.join(logsDir, 'combined.log'), + filename: path.join(logs_dir, 'combined.log'), }), - // Error logs written to a dedicated file + new transports.File({ - filename: path.join(logsDir, 'error.log'), + filename: path.join(logs_dir, 'error.log'), level: 'error', + format: format.combine( + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), + format.errors({ stack: true }), + printf(({ level, message, timestamp, stack, ...meta }) => { + let log = `${timestamp} [${level.toUpperCase()}]: ${message}`; + if (stack) { + log += `\ntrace: ${stack}`; + } + if (Object.keys(meta).length > 0) { + log += `\nmetadata: ${JSON.stringify(meta, null, 2)}`; + } + return log; + }) + ), }), ], });