Compare commits

...
21 Commits
Author SHA1 Message Date
yhqe 8cd9380166 added logo, for snippet command which has some other changes as well (gif support, error handling, bugfixes, etc) 2026-07-13 03:18:07 -05:00
st 623300ea4a added snippet comband 2026-07-13 02:28:51 -05:00
st 2d29b80b50 fixed merge conflict bullshittery with autoban 2026-06-30 00:19:41 -05:00
st 55ebff6bf2 stopped tracking items.json 2026-06-30 00:19:14 -05:00
st 0304eba0b3 Merge branch 'main' of https://git.radium.wtf/st/twitter 2026-06-28 21:56:14 -05:00
st 105dd2dc4d Merge branch 'main' of https://git.radium.wtf/st/twitter 2026-06-28 21:55:39 -05:00
yhqe 3858a7e265 added minecraft server status command 2026-06-07 17:01:09 -05:00
yhqe 6d2a1a484f added minecraft server command (tracks server status) 2026-06-07 16:58:43 -05:00
yhqe 554ab445e5 Merge branch 'main' of https://github.com/yhqe/twitter_v3 2026-03-22 02:15:19 -05:00
yhqe f637673a4e lock, mute, prefix, tempban 2026-03-22 02:15:16 -05:00
st 94bf054772 first on gitea 2026-03-21 13:25:24 -05:00
yhqe 1a5aa6abe5 no batch files.... 2026-03-20 13:18:00 -05:00
yhqe 27894dac21 changed back to proxy 2026-03-15 20:43:22 -05:00
yhqe ebc644e269 d 2026-03-15 19:06:45 -05:00
yhqe 55df89c02b emergency switched roblox api calls to proxies since roblox is blocking datacenter ips 2026-03-15 03:57:39 -05:00
yhqe 670512944c f 2026-03-15 03:45:16 -05:00
yhqe a27bfb63e8 foprntoie 2026-03-12 23:28:09 -05:00
yhqe 09ddaba972 f 2026-03-12 22:12:53 -05:00
yhqe 9f9c852a36 fortnite 2026-03-12 18:38:28 -05:00
ian 238dce1738 Merge pull request #1 from yhqe/copilot/create-discord-js-bot-base
[WIP] Add discord.js bot base with traditional command style
2026-03-12 18:33:40 -05:00
copilot-swe-agent[bot]andyhqe e8049008eb Add Discord.js bot base with prefix commands and Winston logging
Co-authored-by: yhqe <139268606+yhqe@users.noreply.github.com>
2026-03-12 23:32:21 +00:00
34 changed files with 3987 additions and 3 deletions

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
# Discord Bot Token — get yours at https://discord.com/developers/applications
BOT_TOKEN=your_bot_token_here
# Command prefix (default: !)
PREFIX=!
# Guild ID for test server (optional — used for dev utilities)
GUILD_ID=your_guild_id_here
+8 -1
View File
@@ -6,6 +6,8 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
*.bat
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
@@ -65,6 +67,9 @@ web_modules/
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity
# database
db.json
# dotenv environment variable files # dotenv environment variable files
.env .env
.env.* .env.*
@@ -136,4 +141,6 @@ dist
# Vite logs files # Vite logs files
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-*
items.json
db.json
+121 -2
View File
@@ -1,2 +1,121 @@
# twitter_v3 # twitter (v3)
discord moderation bort
a discord.js v14 moderation (+ more) bot
---
## features
- prefix command system — commands triggered by a configurable prefix (default `!`)
- auto-loading — commands and events are loaded automatically from their folders
- aliases — each command can define one or more aliases
- winston logging — coloured console output + rolling log files (`logs/combined.log`, `logs/error.log`)
- global error handling — unhandled rejections and uncaught exceptions are caught and logged
---
## project structure
```
src/
├── index.js # entry point — creates the client, loads handlers
├── commands/
│ ├── help.js # !help — lists commands or shows command detail
│ └── ping.js # !ping — latency check
├── events/
│ ├── ready.js # fires once when the bot is online
│ └── messageCreate.js # parses every message for prefix commands
├── handlers/
│ ├── commandHandler.js # reads src/commands/ and registers commands
│ └── eventHandler.js # reads src/events/ and registers discord events
└── utils/
└── logger.js # winston logger instance
logs/ # created automatically at runtime (git-ignored)
```
---
## setup
### 1. clone & install dependencies
```bash
git clone https://github.com/yhqe/twitter_v3.git
cd twitter_v3
npm install
```
### 2. configure environment variables
```bash
cp .env.example .env
```
open `.env` and fill in:
| variable | required | description |
|-------------|----------|------------------------------------------------------|
| `BOT_TOKEN` | yes | your bot token from the discord developer portal |
| `PREFIX` | no | command prefix (default: `!`) |
| `GUILD_ID` | no | test server id (handy for dev stuff) |
### 3. enable the message content intent
go to your application in the [discord developer portal](https://discord.com/developers/applications), open **bot → privileged gateway intents**, and enable **message content intent**. you need this for prefix commands.
### 4. start the bot
```bash
# production
npm start
# development (auto-restarts on file changes, node.js 18+)
npm run dev
```
---
## adding commands
create a file in `src/commands/`. the handler will load it automatically on next start.
```js
'use strict';
module.exports = {
name: 'example', // primary command name (also used as the key)
description: 'An example command.',
aliases: ['ex'], // optional aliases
usage: 'example <text>', // shown in !help example
async execute(client, message, args) {
await message.reply(`You said: ${args.join(' ')}`);
},
};
```
## adding events
create a file in `src/events/`. set `once: true` if it should fire only once (e.g. `ready`).
```js
'use strict';
const { Events } = require('discord.js');
module.exports = {
name: Events.GuildMemberAdd,
async execute(client, member) {
console.log(`${member.user.tag} joined ${member.guild.name}`);
},
};
```
---
## logging
log files are written to the `logs/` directory (git-ignored). the log level defaults to `info` and can be overridden with the `LOG_LEVEL` environment variable (e.g. `LOG_LEVEL=debug npm start`).
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+8
View File
@@ -0,0 +1,8 @@
{
"autoroles": {
"1133441692196950101": "1192595134026616894"
},
"autobans": {
"1133441692196950101": "1264634623950848158"
}
}
+609
View File
@@ -0,0 +1,609 @@
{
"name": "twitter_v3",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "twitter_v3",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"discord.js": "^14.25.1",
"dotenv": "^17.3.1",
"winston": "^3.19.0"
}
},
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
"license": "MIT",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
"integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
"license": "MIT",
"dependencies": {
"@so-ric/colorspace": "^1.1.6",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"node_modules/@discordjs/builders": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
"integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/formatters": "^0.6.2",
"@discordjs/util": "^1.2.0",
"@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "^0.38.33",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.4",
"tslib": "^2.6.3"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/collection": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=16.11.0"
}
},
"node_modules/@discordjs/formatters": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
"integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
"license": "Apache-2.0",
"dependencies": {
"discord-api-types": "^0.38.33"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/rest": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz",
"integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.1",
"@discordjs/util": "^1.1.1",
"@sapphire/async-queue": "^1.5.3",
"@sapphire/snowflake": "^3.5.3",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.38.16",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/util": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
"license": "Apache-2.0",
"dependencies": {
"discord-api-types": "^0.38.33"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/ws": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.0",
"@discordjs/rest": "^2.5.1",
"@discordjs/util": "^1.1.0",
"@sapphire/async-queue": "^1.5.2",
"@types/ws": "^8.5.10",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "^0.38.1",
"tslib": "^2.6.2",
"ws": "^8.17.0"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@sapphire/async-queue": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
"license": "MIT",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sapphire/shapeshift": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=v16"
}
},
"node_modules/@sapphire/snowflake": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
"license": "MIT",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@so-ric/colorspace": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
"integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
"license": "MIT",
"dependencies": {
"color": "^5.0.2",
"text-hex": "1.0.x"
}
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@vladfrangu/async_event_emitter": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
"license": "MIT",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/color": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
"integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
"license": "MIT",
"dependencies": {
"color-convert": "^3.1.3",
"color-string": "^2.1.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/color-convert": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
"integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
"license": "MIT",
"dependencies": {
"color-name": "^2.0.0"
},
"engines": {
"node": ">=14.6"
}
},
"node_modules/color-name": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
"integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
"license": "MIT",
"engines": {
"node": ">=12.20"
}
},
"node_modules/color-string": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
"integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
"license": "MIT",
"dependencies": {
"color-name": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/discord-api-types": {
"version": "0.38.42",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.42.tgz",
"integrity": "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ==",
"license": "MIT",
"workspaces": [
"scripts/actions/documentation"
]
},
"node_modules/discord.js": {
"version": "14.25.1",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
"integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/builders": "^1.13.0",
"@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.33",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/dotenv": {
"version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
"license": "MIT"
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash.snakecase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
"license": "MIT"
},
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
"license": "MIT",
"dependencies": {
"@colors/colors": "1.6.0",
"@types/triple-beam": "^1.3.2",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"safe-stable-stringify": "^2.3.1",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/magic-bytes.js": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
"integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
"license": "MIT"
},
"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==",
"license": "MIT"
},
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"license": "MIT",
"dependencies": {
"fn.name": "1.x.x"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"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"
}
],
"license": "MIT"
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
"license": "MIT"
},
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
"license": "MIT",
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/ts-mixer": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/undici": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.0.tgz",
"integrity": "sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/winston": {
"version": "3.19.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
"integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
"license": "MIT",
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.8",
"async": "^3.2.3",
"is-stream": "^2.0.0",
"logform": "^2.7.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"safe-stable-stringify": "^2.3.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.9.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-transport": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
"license": "MIT",
"dependencies": {
"logform": "^2.7.0",
"readable-stream": "^3.6.2",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"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
}
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
{
"name": "twitter_v3",
"version": "0.1.3",
"description": "discord moderation bort",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon --watch src/ src/index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yhqe/twitter_v3.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"bugs": {
"url": "https://github.com/yhqe/twitter_v3/issues"
},
"homepage": "https://github.com/yhqe/twitter_v3#readme",
"dependencies": {
"discord.js": "^14.25.1",
"dotenv": "^17.3.1",
"nodemon": "^3.1.14",
"winston": "^3.19.0",
"winston-console-format": "^1.0.8"
},
"overrides": {
"undici": ">=6.23.0"
}
}
+672
View File
@@ -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: {}
+2
View File
@@ -0,0 +1,2 @@
ignoredBuiltDependencies:
- better-sqlite3
+139
View File
@@ -0,0 +1,139 @@
'use strict';
const { time } = require("discord.js");
const logger = require('../../utils/logger');
async function getAvailableCapes(username) {
const capes =[];
const mojangReq = await Promise.allSettled([
fetch(`https://api.ashcon.app/mojang/v2/user/${username}`),
]);
if (mojangReq.status === 'fulfilled' && mojangReq.value.ok) {
const mojangData = await mojangReq.value.json();
if (mojangData.textures && mojangData.textures.cape) {
capes.push({
source: 'Official (Equipped)',
url: mojangData.textures.cape.url
});
}
}
return capes;
}
module.exports = {
name: 'mc',
description: `request different information about a minecraft user`,
aliases: ['minecraft'],
usage: 'mc <username>',
async execute(client, message, args) {
var embed;
let sent = await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<a:loading_icon:1366587619890302998> fetching...',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
const username = args[0];
if (!username) {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> please provide a valid minecraft username!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return await sent.edit({ content: null, embeds: [embed] });
}
const uuid_res = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`);
const uuid = await uuid_res.json().then(res => res.id).catch(err => {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> cannot fetch uuid!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return null;
});
const res = await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`);
const data = await res.json().then(res => res).catch(err => {
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> cannot fetch data!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return null;
});
getAvailableCapes(`${data.name}`).then(capes => {
logger.debug(`found ${capes.length} cape(s):`);
logger.debug(capes);
});
if (!data) return await sent.edit({ content: null, embeds: [embed] });
const skin = await fetch (`https://minotar.net/helm/${data.name}/420.png`).then(res => res.url).catch(err => null);
const capes = data.properties[0].value;
const decoded_capes = Buffer.from(capes, 'base64').toString('utf-8');
const cape_url = JSON.parse(decoded_capes).textures.CAPE ? JSON.parse(decoded_capes).textures.CAPE.url : null;
logger.debug(`[mc] fetched data for "${username}": ${JSON.stringify(data)}`);
logger.debug(`[mc] fetched cape data for "${username}": ${JSON.stringify(cape_url)}`);
embed = {
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `**[${data.name}](https://namemc.com/profile/${data.name})**`,
fields: [
{ name: 'uuid', value: `${data.id}`, inline: true },
],
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
thumbnail: {
url: skin,
height: 1024,
width: 1024,
},
};
await sent.edit({ content: null, embeds: [embed] });
},
};
+384
View File
@@ -0,0 +1,384 @@
'use strict';
const { time } = require("discord.js");
const path = require('path');
const fs = require('fs');
const logger = require('../../utils/logger');
const env = process.env;
async function fetch_avatar(id) {
const res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${id}&size=420x420&format=Png&isCircular=false`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await res.json();
logger.debug(`[fetch_avatar] ${JSON.stringify(data)}`);
if (data.data && data.data[0] && data.data[0].imageUrl) {
logger.debug(`[fetch_avatar] fetched avatar for user id ${id}: ${data.data[0].imageUrl}`);
return data.data[0].imageUrl;
} else {
return null;
}
}
async function fetch_item_thumb(id) {
const res = await fetch(`https://thumbnails.roproxy.com/v1/assets?assetIds=${id}&returnPolicy=PlaceHolder&size=420x420&format=Png&isCircular=false`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await res.json();
logger.debug(`[fetch_item_thumb] ${JSON.stringify(data)}`);
if (data.data && data.data[0] && data.data[0].imageUrl) {
logger.debug(`[fetch_item_thumb] fetched item thumbnail for item id ${id}: ${data.data[0].imageUrl}`);
return data.data[0].imageUrl;
} else {
return null;
}
}
function to_unix_timestamp(dateString) {
const unixSeconds = Math.floor(new Date(dateString).getTime() / 1000);
logger.debug(unixSeconds);
return unixSeconds;
}
module.exports = {
name: 'rbx',
description: `various commands related to roblox`,
aliases: ['roblox'],
usage: 'rbx user <username>\n.rbx item <item id>\n.rbx group <group id>\n.rbx game <game id>',
async execute(client, message) {
var embed = {};
const args = message.content.split(' ').slice(1);
if (args.length < 2) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please provide a subcommand and an argument!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const subcommand = args[0].toLowerCase();
const argument = args.slice(1).join(' ');
let sent = await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<a:loading_icon:1366587619890302998> fetching...',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
const res = await fetch(`https://users.roblox.com/v1/usernames/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` },
body: JSON.stringify({ usernames: [argument], excludeBannedUsers: false })
});
const data = await res.json();
logger.debug(`[rbx] fetched user data (roblox, standard) for "${argument}": ${JSON.stringify(data)}`);
if (!data.data || !data.data[0]) {
throw new Error(`user ${argument} not found...`);
}
const extra_res = await fetch(`https://users.roblox.com/v1/users/${data.data[0].id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` }
});
const extra_data = await extra_res.json();
logger.debug(`[rbx] fetched user data (roblox, extra) for "${argument}": ${JSON.stringify(extra_data)}`);
const r_res = await fetch(`https://api.rolimons.com/players/v1/playerinfo/${data.data[0].id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
let r_data = {};
if (r_res.ok) {
r_data = await r_res.json();
} else {
logger.warn(`[rbx] Failed to fetch rolimons playerinfo for id ${data.data[0].id}: HTTP ${r_res.status}`);
r_data = {};
}
logger.debug(`[rbx] fetched user data (rolimons) for "${argument}": ${JSON.stringify(r_data)}`);
const ra_res = await fetch(`https://api.rolimons.com/players/v1/playerassets/${data.data[0].id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const ra_data = await ra_res.json();
logger.debug(`[rbx] fetched user data (rolimons, assets) for "${argument}": ${JSON.stringify(ra_data)}`);
if (subcommand === 'user' || subcommand === 'u') {
try {
if (!data.data || !data.data[0]) {
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> could not find a roblox user with that username!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}
} else {
const user = data.data[0];
const avatarUrl = await fetch_avatar(user.id);
const hasCheckmark = user.hasVerifiedBadge;
const hasPremium = r_data.premium;
const isOnline = ra_data.isOnline;
const viewInvRes = await fetch(`https://inventory.roproxy.com/v1/users/${user.id}/can-view-inventory`);
const isInvPublic = (await viewInvRes.json()).canView;
const formatted_desc = (extra_data.description || "no description")
.split('\n')
.map(line => line.trim() ? `-# *${line}*` : '')
.join('\n');
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `**[${user.displayName}](https://www.roblox.com/users/${user.id}/profile)** [(${user.name})](https://www.rolimons.com/player/${user.id}) ${isOnline ? "<:online:1482657396362055826>" : "<:offline:1482657564650115073>"} ${hasPremium ? "<:premium:1482660040358760602>" : ""} ${hasCheckmark ? "<:Verified:1376786225486364823>" : ""}\n${formatted_desc}`,
fields: [
{ name: 'id', value: String(extra_data.id), inline: true },
{ name: 'rap', value: String(r_data.rap), inline: true },
{ name: 'value', value: String(r_data.value), inline: true },
{ name: 'inventory', value: `${isInvPublic ? 'public' : 'private'}`, inline: true },
{ name: 'terminated', value: String(r_data.terminated), inline: true },
{ name: 'date created', value: `<t:${to_unix_timestamp(String(extra_data.created))}:f>`, inline: true },
{ name: 'last online', value: `${isOnline ? "now" : "<t:" + r_data.last_online + ":f>"}`, inline: true },
],
thumbnail: {
url: avatarUrl,
height: 420,
width: 420,
},
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
}
} catch (err) {
logger.error(`[rbx] failed to fetch user data for "${argument}":`, err);
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:no:1481811955592466492> could not fetch data for user "${argument}"!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}
}
} else if (subcommand === 'item') {
try {
const res = await fetch(`https://economy.roproxy.com/v2/assets/${argument}/details`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}`
}
});
const p_res = await fetch(`https://thumbnails.roproxy.com/v1/assets?assetIds=${argument}&returnPolicy=PlaceHolder&size=420x420&format=Png&isCircular=false`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}`
}
});
const resale_res = await fetch(`https://economy.roproxy.com/v1/assets/${argument}/resale-data`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Cookie': `.ROBLOSECURITY=${process.env.ROBLOSECURITY}`
}
});
const item = await res.json();
logger.debug(`[rbx] fetched item data (roblox) for "${argument}": ${JSON.stringify(item)}`);
const resale_data = await resale_res.json();
logger.debug(`[rbx] fetched resale data for "${argument}": ${JSON.stringify(resale_data)}`);
let itemsJson;
try {
const itemsPath = path.resolve(process.cwd(), 'items.json');
const itemsRaw = fs.readFileSync(itemsPath, 'utf8');
itemsJson = JSON.parse(itemsRaw);
if (itemsJson.items && itemsJson.items[argument]) {
logger.debug(`[rbx] found item ${argument} in items.json: ${JSON.stringify(itemsJson.items[argument])}`);
}
} catch (e) {
logger.warn(`[rbx] could not read items.json: ${e.message}`);
}
let item_e = itemsJson.items[argument];
var hasAcronym = false;
if (item_e[1] = "") {
hasAcronym = false;
} else {
hasAcronym = true;
}
const item_thumb = await fetch_item_thumb(item.AssetId);
const is_offsale = item.IsForSale;
const is_limited = item.IsLimited;
const is_limited_unique = item.IsLimitedUnique;
const is_limited_at_all = is_limited || is_limited_unique;
const limited_string = is_limited ? (is_limited_unique ? "true (unique)" : "true") : "false";
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `[${item.Name}](https://roblox.com/catalog/${argument}) ${hasAcronym ? item_e[1] : ""}\n-# *${item.Description}*`,
fields: [
{ name: "id", value: `${item.AssetId}`, inline: true },
{ name: "price", value: `${is_offsale ? "offsale" : item.PriceInRobux}`, inline: true },
{ name: "limited", value: `${limited_string}`, inline: true },
{ name: "rap", value: `${is_limited_at_all ? resale_data.recentAveragePrice : "0"}`, inline: true },
{ name: "creator", value: `${item.Creator.Name}`, inline: true },
],
thumbnail: {
url: item_thumb,
height: 420,
width: 420
},
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
} catch (err) {
logger.error(`[rbx] failed to fetch item data for "${argument}":`, err);
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:no:1481811955592466492> could not fetch data for item "${argument}"!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}
}
} else if (subcommand === 'group') {
try {
const res = await fetch(`https://groups.roproxy.com/v1/groups/${argument}`);
const p_res = await fetch(`https://thumbnails.roproxy.com/v1/groups/icons?groupIds=${argument}&size=420x420&format=Png&isCircular=false`);
if (!res.ok) throw new Error();
if (!p_res.ok) throw new Error();
const group = await res.json();
const p_json = await p_res.json();
logger.debug(`${JSON.stringify(p_json)}`);
const thumb = (p_json && p_json.data && p_json.data[0]) ? p_json.data[0].imageUrl : null;
embed = {
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `**[${group.name}](https://roblox.com/communities/${argument})**\n-# *${group.description || "no description"}*`,
thumbnail: {
url: thumb,
height: 420,
width: 420
},
fields: [
{ name: "members", value: `${group.memberCount}`, inline: true },
{ name: "owner", value: `${group.owner.username}`, inline: true },
{ name: "shout", value: `${group.shout}`, inline: true },
{ name: "open", value: `${group.publicEntryAllowed}`, inline: true },
{ name: "verified", value: `${group.hasVerifiedBadge}`, inline: true },
],
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
} catch (err) {
logger.error(`[rbx] failed to fetch group data for "${argument}":`, err);
embed.title = 'Error';
embed.description = 'Failed to fetch group data.';
}
} else if (subcommand === 'game') {
try {
const res = await fetch(`https://games.roproxy.com/v1/games?universeIds=${argument}`);
if (!res.ok) throw new Error();
const data = await res.json();
if (!data.data || !data.data[0]) {
embed.title = 'Game Not Found';
embed.description = `Could not find a Roblox game with the universe ID **${argument}**.`;
} else {
const game = data.data[0];
embed.title = `Roblox Game: ${game.name}`;
embed.description = game.description || 'No description.';
embed.thumbnail.url = `https://www.roproxy.com/asset-thumbnail/image?assetId=${game.rootPlaceId}&width=420&height=420&format=png`;
embed.fields = [
{ name: 'Creator', value: game.creator.name, inline: true },
{ name: 'Playing', value: game.playing.toLocaleString(), inline: true },
{ name: 'Visits', value: game.visits.toLocaleString(), inline: true }
];
}
} catch (err) {
embed.title = 'Error';
embed.description = 'Failed to fetch game data.';
}
}/* else {
embed.title = 'invalid subcommand';
embed.description = 'Valid subcommands: `user`, `item`, `group`, `game`';
}*/
await sent.edit({ content: null, embeds: [embed] });
},
};
+84
View File
@@ -0,0 +1,84 @@
const { EmbedBuilder } = require('discord.js');
const db = require('../../db.json');
'use strict';
const get_guild_prefix = (guildId) => {
return db.prefixes?.[guildId] || null;
};
module.exports = {
name: 'help',
description: 'lists all available commands or shows info about a specific one',
aliases: ['h', 'commands'],
usage: 'help [command]',
execute(client, message, args) {
var embed;
let prefix = process.env.PREFIX;
if (message.guild) {
const guildPrefix = get_guild_prefix(message.guild.id);
if (guildPrefix) prefix = guildPrefix;
}
if (args.length > 0) {
const name = args[0].toLowerCase();
const command = client.commands.get(name);
if (!command) {
return message.reply(`no command found for \`${name}\`.`);
}
embed = {
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `**${prefix}${command.name}**\n-# ${command.description || 'no description'}`,
fields: [
{ name: 'aliases', value: command.aliases.map((a) => `\`${prefix}${a}\``).join(', ') },
{ name: 'usage', value: `\`${prefix}${command.usage}\`` }
],
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
return message.reply({ embeds: [embed] });
}
const seen = new Set();
const commandList = [];
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'}`);
}
embed = {
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `-# use ${prefix}help <command> 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] });
},
};
+102
View File
@@ -0,0 +1,102 @@
'use strict';
const { time } = require("discord.js");
const fs = require('fs');
const path = require('path');
module.exports = {
name: 'autoban',
description: `automatically ban a user if they join the server`,
aliases: ['ab', 'banish'],
usage: 'ban <user> [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() || await client.users.fetch(args[0]).catch(() => null);
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 autoban!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const reason = args.slice(1).join(' ') || 'autobanned (twitter)';
const dbPath = path.join(__dirname, '..', '..', '..', 'db.json');
let db = {};
if (fs.existsSync(dbPath)) {
db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
}
if (!db.autobans) {
db.autobans = {};
}
db.autobans[message.guild.id] = user.id;
try {
await 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> ${user.username} has been added to the autoban list!`,
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 add the user to the autoban list!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
},
};
+87
View File
@@ -0,0 +1,87 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'ban',
description: `ban a user from the current server`,
aliases: ['banish'],
usage: 'ban <user> [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)';
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(),
}]
});
}
},
};
+88
View File
@@ -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 <user> [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(),
}]
});
}
},
};
+46
View File
@@ -0,0 +1,46 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'lockdown',
description: 'lockdown the the current channel ',
aliases: ['ld', 'lock'],
usage: 'lockdown',
async execute(client, message) {
var embed;
if (!message.member?.permissions?.has('ManageChannels')) {
await message.reply({
embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
const channel = message.channel;
await channel.permissionOverwrites.edit(message.guild.roles.everyone, { SendMessages: false });
await channel.permissionOverwrites.edit(message.guild.roles.cache.find(r => r.permissions.has('Administrator')), { SendMessages: true });
embed = {
color: 0x8aa2b3,
title: '',
description: `<:check:1481811973447356496> ${channel} is now locked!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
await message.reply({ content: null, embeds: [embed] });
},
};
+64
View File
@@ -0,0 +1,64 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'role',
description: `give or take a role from a user`,
aliases: ['r'],
usage: 'role <user> (-)<role>',
async execute(client, message) {
var embed = {
color: 0x8aa2b3,
title: '',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}
if (!message.member.permissions.has('Administrator')) {
embed.description = '<:no:1481811955592466492> you do not have permissions to run this command!';
await message.reply({ content: null, embeds: [embed] });
return;
}
if (!message.mentions.members.size) {
embed.description = '<:no:1481811955592466492> please specify a user to give or take a role from!';
await message.reply({ content: null, embeds: [embed] });
return;
}
const member = message.mentions.members.first();
const roleName = message.content.split(' ').slice(2).join(' ').replace(/^-/, '');
const role = message.guild.roles.cache.find(r => r.name === roleName);
if (!role) {
embed.description = '<:no:1481811955592466492> role not found!';
await message.reply({ content: null, embeds: [embed] });
return;
}
if (message.content.split(' ')[2].startsWith('-')) {
if (!member.roles.cache.has(role.id)) {
embed.description = `<:no:1481811955592466492> ${member} does not have the ${role} role!`;
await message.reply({ content: null, embeds: [embed] });
return;
}
await member.roles.remove(role);
embed.description = `<:check:1481811973447356496> ${role} role has been taken from ${member}!`;
} else {
if (member.roles.cache.has(role.id)) {
embed.description = `<:no:1481811955592466492> ${member} already has the ${role} role!`;
await message.reply({ content: null, embeds: [embed] });
return;
}
await member.roles.add(role);
embed.description = `<:check:1481811973447356496> ${role} role has been given to ${member}!`;
}
await message.reply({ content: null, embeds: [embed] });
},
};
+115
View File
@@ -0,0 +1,115 @@
'use strict';
const { PermissionFlagsBits } = require("discord.js");
module.exports = {
name: 'tempban',
description: `temporarily ban a user from the current server`,
aliases: ['tban'],
usage: 'tempban <user> <duration> [reason]',
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 durationStr = args[1];
const durationMap = { s: 1000, m: 60000, h: 3600000, d: 86400000, w: 604800000 };
const match = durationStr?.match(/^(\d+)([smhdw])$/);
if (!match || !durationMap[match[2]]) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please specify a valid duration (e.g., 1s, 1m, 1d, 1w)!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const duration = parseInt(match[1]) * durationMap[match[2]];
const reason = args.slice(2).join(' ') || 'no reason provided (twitter)';
try {
await message.guild.members.ban(user, { reason: `${reason} (twitter, moderator: ${message.author.username})` });
setTimeout(() => {
message.guild.bans.remove(user, 'temporary ban expired (twitter)');
}, duration);
message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:check:1481811973447356496> ${user.username} has been temporarily banned for ${durationStr}!`,
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(),
}]
});
}
},
};
+116
View File
@@ -0,0 +1,116 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'timeout',
description: `timeout a user`,
aliases: ['mute'],
usage: 'timeout <user> <duration>',
async execute(client, message, args) {
var embed;
if (!message.member.permissions.has('ModerateMembers')) {
await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
if (!message.mentions.members.size) {
await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> please specify a user to timeout!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
const member = message.mentions.members.first();
const durationStr = args[1];
const durationMap = { s: 1000, m: 60000, h: 3600000, d: 86400000, w: 604800000 };
const match = durationStr?.match(/^(\d+)([smhdw])$/);
if (!match || !durationMap[match[2]]) {
return message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> please specify a valid duration (e.g., 1s, 1m, 1d, 1w)!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
}
const duration = parseInt(match[1]) * durationMap[match[2]];
if (!duration) {
await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> please specify a valid duration for the timeout!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
try {
await member.timeout(duration);
embed = {
color: 0x8aa2b3,
title: '',
description: `<:check:1481811973447356496> ${member} has been timed out for ${durationStr}!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
} catch (error) {
console.error(error);
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> an error occurred while trying to timeout the user!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
}
await message.reply({ content: null, embeds: [embed] });
},
};
+50
View File
@@ -0,0 +1,50 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'unlockdown',
description: 'unlock the current channel ',
aliases: ['ud', 'unlock'],
usage: 'unlockdown',
async execute(client, message) {
var embed;
if (!message.member?.permissions?.has('ManageChannels')) {
await message.reply({
embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
const channel = message.channel;
await channel.permissionOverwrites.edit(
message.guild.roles.everyone,
{ SendMessages: null }
);
embed = {
color: 0x8aa2b3,
title: '',
description: `<:check:1481811973447356496> ${channel} is now unlocked!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
await message.reply({ content: null, embeds: [embed] });
},
};
+92
View File
@@ -0,0 +1,92 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'untimeout',
description: `untimeout a user`,
aliases: ['unmute'],
usage: 'untimeout <user>',
async execute(client, message, args) {
var embed;
if (!message.member.permissions.has('ModerateMembers')) {
await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
if (!message.mentions.members.size || !message.mentions.members.first().communicationDisabledUntil) {
await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> that user is not timed out!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
if (!message.mentions.members.size) {
await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> please specify a user to timeout!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
return;
}
const member = message.mentions.members.first();
try {
await member.timeout(null);
embed = {
color: 0x8aa2b3,
title: '',
description: `<:check:1481811973447356496> ${member} has been untimed out!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
} catch (error) {
console.error(error);
embed = {
color: 0x8aa2b3,
title: '',
description: '<:no:1481811955592466492> an error occurred while trying to untimeout the user!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
}
await message.reply({ content: null, embeds: [embed] });
},
};
+370
View File
@@ -0,0 +1,370 @@
'use strict';
const fs = require('fs');
const fsp = require('fs/promises');
const os = require('os');
const path = require('path');
const { spawn } = require('child_process');
const { pipeline } = require('stream/promises');
const { Readable } = require('stream');
const logger = require('../../utils/logger');
function isMp3Attachment(attachment) {
const name = (attachment.name || '').toLowerCase();
const contentType = (attachment.contentType || '').toLowerCase();
return name.endsWith('.mp3') || contentType.startsWith('audio/');
}
function isImageAttachment(attachment) {
const name = (attachment.name || '').toLowerCase();
const contentType = (attachment.contentType || '').toLowerCase();
return (
name.endsWith('.png') ||
name.endsWith('.jpg') ||
name.endsWith('.jpeg') ||
name.endsWith('.webp') ||
(contentType.startsWith('image/') && contentType !== 'image/gif')
);
}
function isGifAttachment(attachment) {
const name = (attachment.name || '').toLowerCase();
const contentType = (attachment.contentType || '').toLowerCase();
return name.endsWith('.gif') || contentType === 'image/gif';
}
function isVideoAttachment(attachment) {
const name = (attachment.name || '').toLowerCase();
const contentType = (attachment.contentType || '').toLowerCase();
return (
name.endsWith('.mp4') ||
name.endsWith('.mov') ||
name.endsWith('.mkv') ||
name.endsWith('.webm') ||
name.endsWith('.m4v') ||
contentType.startsWith('video/')
);
}
function runProcess(command, args) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { stdio: ['ignore', 'ignore', 'pipe'] });
let stderr = '';
child.stderr.on('data', (chunk) => {
stderr += chunk.toString();
});
child.on('error', reject);
child.on('close', (code) => {
if (code === 0) {
resolve();
return;
}
reject(new Error(stderr || `${command} exited with code ${code}`));
});
});
}
function isRetryableFetchError(err) {
const code = err?.code || err?.cause?.code;
const name = err?.name;
const message = String(err?.message || '');
return (
code === 'UND_ERR_SOCKET' ||
code === 'ECONNRESET' ||
code === 'EPIPE' ||
code === 'ETIMEDOUT' ||
name === 'SocketError' ||
name === 'FetchError' ||
/other side closed/i.test(message)
);
}
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function downloadAttachment(attachment, filePath) {
const attempts = 3;
for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
const response = await fetch(attachment.url);
if (!response.ok || !response.body) {
throw new Error(`failed to download ${attachment.name}: HTTP ${response.status}`);
}
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(filePath));
return;
} catch (err) {
await fsp.rm(filePath, { force: true }).catch(() => null);
if (attempt < attempts && isRetryableFetchError(err)) {
logger.warn(
`[snippet] attachment download failed, retrying (${attempt}/${attempts}) for ${attachment.name || 'unknown'}: ${err.message || err}`,
);
await sleep(250 * attempt);
continue;
}
throw new Error(`failed to download ${attachment.name}: ${err.message || err}`);
}
}
}
async function getDuration(filePath) {
const probe = spawn('ffprobe', [
'-v', 'error',
'-show_entries', 'format=duration',
'-of', 'default=noprint_wrappers=1:nokey=1',
filePath,
], { stdio: ['ignore', 'pipe', 'pipe'] });
let stdout = '';
let stderr = '';
probe.stdout.on('data', (chunk) => {
stdout += chunk.toString();
});
probe.stderr.on('data', (chunk) => {
stderr += chunk.toString();
});
const exitCode = await new Promise((resolve, reject) => {
probe.on('error', reject);
probe.on('close', resolve);
});
if (exitCode !== 0) {
throw new Error(stderr || 'failed to read media duration');
}
const duration = Number.parseFloat(stdout.trim());
if (!Number.isFinite(duration) || duration <= 0) {
throw new Error('invalid media duration');
}
return duration;
}
function buildEmbed(client, message, description, fields = []) {
const embed = {
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description,
footer: {
text: `twitter @ ${message.guild?.name || 'dms'}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
};
if (fields.length > 0) {
embed.fields = fields;
}
return embed;
}
function getErrorMessage(err) {
if (err instanceof Error) {
return err.stack || err.message;
}
if (typeof err === 'string') {
return err;
}
return 'unknown error';
}
const logoPath = path.resolve(__dirname, '../../../assets/logo.png');
function buildVideoFilter() {
return [
'[0:v]scale=trunc(iw/2)*2:trunc(ih/2)*2[base]',
'[2:v]format=rgba,colorchannelmixer=aa=0.5,scale=trunc(iw*0.12/2)*2:-1,rotate=(PI/2)*t:ow=rotw(iw):oh=roth(ih):c=none[logo]',
'[base][logo]overlay=x=W-w-16:y=H-h-16:shortest=1[v]',
].join(';');
}
module.exports = {
name: 'snippet',
description: 'create a snippet with an image/video and an mp3',
usage: 'snippet <seconds> [mp3 attachment] [image or video attachment]',
async execute(client, message, args = []) {
const requestedLength = Number.parseInt(args[0], 10);
if (!Number.isInteger(requestedLength) || requestedLength <= 0) {
return message.reply({
content: null,
embeds: [buildEmbed(
client,
message,
'<:no:1481811955592466492> provide a clip length in seconds, then attach one mp3 and either one image or one video file.',
)],
});
}
const attachments = [...message.attachments.values()];
const mp3Attachment = attachments.find(isMp3Attachment);
const imageAttachment = attachments.find(isImageAttachment);
const gifAttachment = attachments.find(isGifAttachment);
const videoAttachment = attachments.find(isVideoAttachment);
const visualAttachment = imageAttachment || gifAttachment || videoAttachment;
const hasMultipleVisualTypes = [imageAttachment, gifAttachment, videoAttachment].filter(Boolean).length > 1;
if (!mp3Attachment || !visualAttachment || hasMultipleVisualTypes) {
return message.reply({
content: null,
embeds: [buildEmbed(
client,
message,
'<:no:1481811955592466492> attach one mp3 file and either one image, one gif, or one video file, but not more than one visual file.',
)],
});
}
const sent = await message.reply({
content: null,
embeds: [buildEmbed(
client,
message,
'<a:loading_icon:1366587619890302998> building your snippet...',
)],
});
const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'twitter-snippet-'));
const audioPath = path.join(tempDir, mp3Attachment.name || 'input.mp3');
const visualPath = path.join(tempDir, visualAttachment.name || (imageAttachment ? 'input.png' : gifAttachment ? 'input.gif' : 'input.mp4'));
const outputPath = path.join(tempDir, 'snippet.mp4');
try {
await Promise.all([
downloadAttachment(mp3Attachment, audioPath),
downloadAttachment(visualAttachment, visualPath),
]);
const audioDuration = await getDuration(audioPath);
const isStillImageSource = Boolean(imageAttachment && imageAttachment === visualAttachment);
const isGifSource = Boolean(gifAttachment && gifAttachment === visualAttachment);
const isLoopedVisualSource = isStillImageSource || isGifSource;
const visualDuration = isLoopedVisualSource ? Infinity : await getDuration(visualPath);
if (requestedLength > audioDuration || requestedLength > visualDuration) {
throw new Error('requested length is longer than one of the attached files');
}
const clipLength = requestedLength;
if (isLoopedVisualSource) {
const audioStart = audioDuration === clipLength
? 0
: Math.random() * (audioDuration - clipLength);
await runProcess('ffmpeg', [
'-y',
...(isStillImageSource ? ['-loop', '1'] : ['-stream_loop', '-1']),
'-i', visualPath,
'-ss', audioStart.toFixed(3),
'-i', audioPath,
'-loop', '1',
'-i', logoPath,
'-t', clipLength.toFixed(3),
'-filter_complex', buildVideoFilter(),
'-map', '[v]',
'-map', '1:a:0',
'-c:v', 'libx264',
'-preset', 'veryfast',
'-crf', '20',
'-c:a', 'aac',
'-b:a', '192k',
'-pix_fmt', 'yuv420p',
'-movflags', '+faststart',
outputPath,
]);
} else {
const videoStart = visualDuration === clipLength
? 0
: Math.random() * (visualDuration - clipLength);
const audioStart = audioDuration === clipLength
? 0
: Math.random() * (audioDuration - clipLength);
await runProcess('ffmpeg', [
'-y',
'-ss', videoStart.toFixed(3),
'-i', visualPath,
'-ss', audioStart.toFixed(3),
'-i', audioPath,
'-loop', '1',
'-i', logoPath,
'-t', clipLength.toFixed(3),
'-filter_complex', buildVideoFilter(),
'-map', '[v]',
'-map', '1:a:0',
'-c:v', 'libx264',
'-preset', 'veryfast',
'-crf', '20',
'-c:a', 'aac',
'-b:a', '192k',
'-pix_fmt', 'yuv420p',
'-movflags', '+faststart',
outputPath,
]);
}
const finalEmbed = buildEmbed(
client,
message,
'<:check:1481811973447356496> clip finished successfully.',
[
{ name: 'audio source', value: `**${mp3Attachment.name || 'unknown'}**`, inline: true },
{ name: 'visual source', value: `**${visualAttachment.name || 'unknown'}**`, inline: true },
{ name: 'clip length', value: `**${clipLength.toFixed(2)}s**`, inline: true },
],
);
await sent.edit({
content: null,
embeds: [finalEmbed],
files: [{ attachment: outputPath, name: 'snippet.mp4' }],
});
} catch (err) {
const errorMessage = getErrorMessage(err);
logger.error('[snippet] failed to build clip', err instanceof Error ? err : new Error(String(err)));
await sent.edit({
content: null,
embeds: [buildEmbed(
client,
message,
'<:no:1481811955592466492> failed to build the clip.',
[
{ name: 'reason', value: `**${errorMessage.slice(0, 1000)}**` },
],
)],
}).catch((editErr) => {
logger.error('[snippet] failed to edit error reply', editErr instanceof Error ? editErr : new Error(String(editErr)));
});
} finally {
await fsp.rm(tempDir, { recursive: true, force: true }).catch(() => null);
}
},
};
+84
View File
@@ -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 <role>',
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(),
}]
});
}
};
+67
View File
@@ -0,0 +1,67 @@
'use strict';
const { time } = require("discord.js");
const { exec } = require('child_process');
module.exports = {
name: 'mcserver',
description: `shhhhh`,
aliases: [''],
usage: 'mcserver',
async execute(client, message) {
const sent = await message.reply({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<a:loading_icon:1366587619890302998> pinging...',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
const online = await new Promise((resolve) => {
exec(`ping -n 1 mc.radium.wtf`, (error) => {
resolve(!error);
});
});
let embed;
if (online) {
embed = {
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: 'server is online!',
fields: [
{ name: 'ip', value: `mc.radium.wtf`, inline: true },
{ name: 'version', value: `26.1.2 (latest)`, inline: true },
],
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
thumbnail: {
url: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.freebiesupply.com%2Flogos%2Flarge%2F2x%2Fminecraft-1-logo-png-transparent.png&f=1&nofb=1&ipt=9aebb419eba433a6b68129b3cb0aeebee3d1f40d3c7ea181b1da41fead97acdd",
height: 2637,
width: 2400,
},
};
}
await sent.edit({ content: null, embeds: [embed] });
},
};
+58
View File
@@ -0,0 +1,58 @@
'use strict';
const { time } = require("discord.js");
module.exports = {
name: 'ping',
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({
content: null, embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<a:loading_icon:1366587619890302998> pinging...',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date(),
}]
});
const latency = sent.createdTimestamp - message.createdTimestamp;
const apiLatency = Math.round(client.ws.ping);
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] });
},
};
+137
View File
@@ -0,0 +1,137 @@
'use strict';
const { time } = require("discord.js");
const fs = require('node:fs');
const path = require('node:path');
const DB_PATH = path.join(__dirname, '../../../db.json');
const readDb = () => {
try {
return JSON.parse(fs.readFileSync(DB_PATH, 'utf8'));
} catch {
return {};
}
};
const writeDb = (db) => {
fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2), 'utf8');
};
module.exports = {
name: 'prefix',
description: 'change the server command prefix',
aliases: ['setprefix'],
usage: 'prefix <new prefix>',
execute: async (client, message, args = []) => {
var embed;
if (!message.guild) {
await message.reply({
embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> this command can only be used in a server!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date()
}]
});
return;
}
if (!message.member?.permissions?.has('ManageGuild')) {
await message.reply({
embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date()
}]
});
return;
}
let newPrefix = args[0];
if (!newPrefix) {
const parts = message.content.trim().split(/\s+/);
newPrefix = parts[1];
}
if (!newPrefix) {
await message.reply({
embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> you must provide a new prefix!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date()
}]
});
return;
}
if (newPrefix.length > 5) {
await message.reply({
embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: '<:no:1481811955592466492> prefix must be 5 characters or less!',
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date()
}]
});
return;
}
const db = readDb();
db.prefixes = db.prefixes || {};
db.prefixes[message.guild.id] = newPrefix;
writeDb(db);
await message.reply({
embeds: [{
color: 0x8aa2b3,
title: '',
author: {
name: message.author.username,
icon_url: message.author.displayAvatarURL(),
},
description: `<:check:1481811973447356496> prefix for **${message.guild.name}** updated to \`${newPrefix}\`!`,
footer: {
text: `twitter @ ${message.guild.name}`,
icon_url: client.user.displayAvatarURL(),
},
timestamp: new Date()
}]
});
}
};
void time;
+61
View File
@@ -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 <number>',
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);
},
};
+50
View File
@@ -0,0 +1,50 @@
'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;
logger.info(`member "${member.user.tag}" has joined guild "${member.guild.name}"`);
// autorole
{
if (!roleId) {
logger.info(`no autorole is set up in "${member.guild.name}"`);
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;
}
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}`));
}
// autoban
{
const autoban = db.autobans ? db.autobans[member.guild.id] : null;
if (!autoban) {
logger.info(`no autoban is set up for ${member.user.tag} in "${member.guild.name}"`);
return;
}
if (member.user.tag === autoban) {
member.ban({ reason: 'autobanned (twitter)' })
.then(() => logger.info(`autobanned user "${member.user.tag}" from guild "${member.guild.name}"`))
.catch(error => logger.error(`failed to autoban user "${member.user.tag}" from guild "${member.guild.name}": ${error}`));
} else {
logger.info(`user "${member.user.tag}" is not on the autoban list for "${member.guild.name}"`);
}
}
};
+61
View File
@@ -0,0 +1,61 @@
'use strict';
const { Events } = require('discord.js');
const logger = require('../utils/logger');
const db = require('../../db.json'); // adjust path as needed
const get_guild_prefix = (guildId) => {
return db.prefixes?.[guildId] || null;
};
module.exports = {
name: Events.MessageCreate,
async execute(client, message) {
if (message.author.bot) return;
let prefix = process.env.PREFIX || '!';
if (message.guild) {
const guildPrefix = get_guild_prefix(message.guild.id);
if (guildPrefix) prefix = guildPrefix;
}
if (!message.content.startsWith(prefix)) return;
const args = message.content.slice(prefix.length).trim().split(/\s+/);
const commandName = args.shift().toLowerCase();
const command = client.commands.get(commandName);
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'}`,
);
try {
await command.execute(client, message, args);
} catch (err) {
logger.error(`[command] error executing "${commandName}":`, err);
await message
.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);
}
},
};
+35
View File
@@ -0,0 +1,35 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { ActivityType, Events } = require('discord.js');
const logger = require('../utils/logger');
module.exports = {
name: Events.ClientReady,
once: true,
execute(client) {
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'}`);
fetch('https://api.rolimons.com/items/v2/itemdetails')
.then(res => res.ok ? res.json() : Promise.reject(new Error('Failed to fetch rolimons item list')))
.then(data => {
fs.writeFileSync(path.join(process.cwd(), 'items.json'), JSON.stringify(data, null, 2));
})
.catch(err => logger.error('[ready] error fetching rolimons item list:', err));
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(`watching over ${client.guilds.cache.size} servers`, { type: ActivityType.Watching });
},
};
+60
View File
@@ -0,0 +1,60 @@
'use strict';
const fs = require('fs');
const path = require('path');
const logger = require('../utils/logger');
/**
* @param {import('discord.js').Client} client
*/
function loadCommands(client) {
const commandsPath = path.join(__dirname, '..', 'commands');
function getAllCommandFiles(dir) {
let results = [];
const list = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of list) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results = results.concat(getAllCommandFiles(fullPath));
} else if (entry.isFile() && entry.name.endsWith('.js')) {
results.push(path.relative(commandsPath, fullPath));
}
}
return results;
}
const commandFiles = getAllCommandFiles(commandsPath);
let loaded = 0;
for (const file of commandFiles) {
try {
const command = require(path.join(commandsPath, file));
if (!command.name || typeof command.execute !== 'function') {
logger.warn(
`[commandhandler] skipping ${file}: missing "name" or "execute"`,
);
continue;
}
client.commands.set(command.name.toLowerCase(), command);
if (Array.isArray(command.aliases)) {
for (const alias of command.aliases) {
client.commands.set(alias.toLowerCase(), command);
}
}
logger.debug(`[commandhandler] loaded command: ${command.name}`);
loaded++;
} catch (err) {
logger.error(`[commandhandler] failed to load ${file}:`, err);
}
}
logger.info(`[commandhandler] ${loaded} command(s) loaded`);
}
module.exports = { loadCommands };
+53
View File
@@ -0,0 +1,53 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { Events } = require('discord.js');
const guildMemberAddEvent = require('../events/guildMemberAdd');
const logger = require('../utils/logger');
/**
* Loads all event files from src/events and registers them on the client.
* Each event file must export: { name, once?, execute }
*
* @param {import('discord.js').Client} client
*/
function loadEvents(client) {
const eventsPath = path.join(__dirname, '..', 'events');
const eventFiles = fs
.readdirSync(eventsPath)
.filter((file) => file.endsWith('.js'));
let loaded = 0;
for (const file of eventFiles) {
try {
const event = require(path.join(eventsPath, file));
if (!event.name || typeof event.execute !== 'function') {
logger.warn(
`[eventhandler] skipping ${file}: missing "name" or "execute"`,
);
continue;
}
if (event.once) {
client.once(event.name, (...args) => event.execute(client, ...args));
} else {
client.on(event.name, (...args) => event.execute(client, ...args));
}
logger.debug(`[eventhandler] registered event: ${event.name}`);
loaded++;
} catch (err) {
logger.error(`[eventhandler] failed to load ${file}:`, err);
}
}
client.on(Events.GuildMemberAdd, guildMemberAddEvent.bind(null, client));
loaded++;
logger.info(`[eventhandler] ${loaded} event(s) registered`);
}
module.exports = { loadEvents };
+50
View File
@@ -0,0 +1,50 @@
'use strict';
require('dotenv').config();
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const logger = require('./utils/logger');
const { loadCommands } = require('./handlers/commandHandler');
const { loadEvents } = require('./handlers/eventHandler');
// Validate required environment variables
if (!process.env.BOT_TOKEN) {
logger.error(
'BOT_TOKEN is not set. Copy .env.example to .env and fill in your token.',
);
process.exit(1);
}
// Create the Discord client with the intents needed for prefix commands
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, // Required to read message content for prefix commands
GatewayIntentBits.DirectMessages,
],
});
// Attach a Collection to store commands
client.commands = new Collection();
// Load commands and events
loadCommands(client);
loadEvents(client);
// Global error handling
process.on('unhandledRejection', (err) => {
logger.error('[process] unhandled rejection:', err);
});
process.on('uncaughtException', (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);
process.exit(1);
});
+74
View File
@@ -0,0 +1,74 @@
'use strict';
const { createLogger, format, transports } = require('winston');
const { consoleFormat } = require("winston-console-format");
const { printf } = format;
const path = require('path');
const fs = require('fs');
const logs_dir = path.join(__dirname, '..', '..', 'logs');
if (!fs.existsSync(logs_dir)) {
fs.mkdirSync(logs_dir, { recursive: true });
}
const logger = createLogger({
level: "silly",
format: format.combine(
format.timestamp(),
format.ms(),
format.errors({ stack: true }),
format.splat(),
format.json({ space: 2 })
),
transports: [
new transports.Console({
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,
},
})
),
}),
new transports.File({
filename: path.join(logs_dir, 'combined.log'),
format: format.combine(
format.timestamp(),
format.ms(),
format.errors({ stack: true }),
format.splat(),
format.json({ space: 2 })
),
}),
new transports.File({
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;
})
),
}),
],
});
module.exports = logger;