Discord bot to open dong files

Compare changes

Choose any two refs to compare.

+1 -2
.env.template
···
TOKEN="<Your Discord Bot Token>"
-
CLIENT="app id"
-
GUILD="dev server"
+
CLIENT="app id"
+7
.gitignore
···
# Finder (MacOS) folder config
.DS_Store
+
lib/index.js
+
lib/sync.js
+
lib/commands/dong/create.js
+
lib/commands/dong/open.js
+
lib/commands/util/ping.js
+
lib/lib/dong-io.js
+
lib/lib/download.js
+3
.vscode/settings.json
···
+
{
+
"deno.enable": true
+
}
+7
Dockerfile
···
+
FROM denoland/deno:latest
+
WORKDIR /app
+
+
COPY ./ /app
+
RUN deno install
+
+
CMD deno --env-file --allow-env --allow-read=./ --allow-net=discord.com,gateway.discord.gg,cdn.discordapp.com src/index.ts
-15
README.md
···
-
# discord
-
-
To install dependencies:
-
-
```bash
-
bun install
-
```
-
-
To run:
-
-
```bash
-
bun run index.ts
-
```
-
-
This project was created using `bun init` in bun v1.2.4. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
-74
bun.lock
···
-
{
-
"lockfileVersion": 1,
-
"workspaces": {
-
"": {
-
"name": "discord",
-
"dependencies": {
-
"discord.js": "^14.18.0",
-
},
-
"devDependencies": {
-
"@types/bun": "latest",
-
},
-
"peerDependencies": {
-
"typescript": "^5",
-
},
-
},
-
},
-
"packages": {
-
"@discordjs/builders": ["@discordjs/builders@1.10.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.0", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.37.119", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng=="],
-
-
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
-
-
"@discordjs/formatters": ["@discordjs/formatters@0.6.0", "", { "dependencies": { "discord-api-types": "^0.37.114" } }, "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw=="],
-
-
"@discordjs/rest": ["@discordjs/rest@2.4.3", "", { "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.37.119", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.1" } }, "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA=="],
-
-
"@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
-
-
"@discordjs/ws": ["@discordjs/ws@1.2.1", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.4.3", "@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.37.119", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ=="],
-
-
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
-
-
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
-
-
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
-
-
"@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="],
-
-
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
-
-
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
-
-
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="],
-
-
"bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
-
-
"discord-api-types": ["discord-api-types@0.37.119", "", {}, "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg=="],
-
-
"discord.js": ["discord.js@14.18.0", "", { "dependencies": { "@discordjs/builders": "^1.10.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.0", "@discordjs/rest": "^2.4.3", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.1", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.37.119", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "tslib": "^2.6.3", "undici": "6.21.1" } }, "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw=="],
-
-
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
-
-
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
-
-
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
-
-
"magic-bytes.js": ["magic-bytes.js@1.10.0", "", {}, "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="],
-
-
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
-
-
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
-
-
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
-
-
"undici": ["undici@6.21.1", "", {}, "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ=="],
-
-
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
-
-
"ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="],
-
-
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
-
-
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
-
}
-
}
+12
deno.json
···
+
{
+
"tasks": {
+
"dev": "deno run --watch --env-file --allow-env --allow-read=./ --allow-net=discord.com,gateway.discord.gg,cdn.discordapp.com src/index.ts",
+
"main": "deno run --env-file --allow-env --allow-read=./ --allow-net=discord.com,gateway.discord.gg,cdn.discordapp.com src/index.ts",
+
"sync": "deno run --env-file --allow-env --allow-read=./src --allow-net=discord.com src/sync.js"
+
},
+
"imports": {
+
"@std/assert": "jsr:@std/assert@1",
+
"discord.js": "npm:discord.js@^14.25.1",
+
"mime": "npm:mime@^4.1.0"
+
}
+
}
+167
deno.lock
···
+
{
+
"version": "5",
+
"specifiers": {
+
"jsr:@std/assert@1": "1.0.16",
+
"jsr:@std/internal@^1.0.12": "1.0.12",
+
"npm:discord.js@^14.25.1": "14.25.1",
+
"npm:mime@^4.1.0": "4.1.0"
+
},
+
"jsr": {
+
"@std/assert@1.0.16": {
+
"integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532",
+
"dependencies": [
+
"jsr:@std/internal"
+
]
+
},
+
"@std/internal@1.0.12": {
+
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
+
}
+
},
+
"npm": {
+
"@discordjs/builders@1.13.1": {
+
"integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
+
"dependencies": [
+
"@discordjs/formatters",
+
"@discordjs/util",
+
"@sapphire/shapeshift",
+
"discord-api-types",
+
"fast-deep-equal",
+
"ts-mixer",
+
"tslib"
+
]
+
},
+
"@discordjs/collection@1.5.3": {
+
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="
+
},
+
"@discordjs/collection@2.1.1": {
+
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="
+
},
+
"@discordjs/formatters@0.6.2": {
+
"integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
+
"dependencies": [
+
"discord-api-types"
+
]
+
},
+
"@discordjs/rest@2.6.0": {
+
"integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==",
+
"dependencies": [
+
"@discordjs/collection@2.1.1",
+
"@discordjs/util",
+
"@sapphire/async-queue",
+
"@sapphire/snowflake",
+
"@vladfrangu/async_event_emitter",
+
"discord-api-types",
+
"magic-bytes.js",
+
"tslib",
+
"undici"
+
]
+
},
+
"@discordjs/util@1.2.0": {
+
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
+
"dependencies": [
+
"discord-api-types"
+
]
+
},
+
"@discordjs/ws@1.2.3": {
+
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
+
"dependencies": [
+
"@discordjs/collection@2.1.1",
+
"@discordjs/rest",
+
"@discordjs/util",
+
"@sapphire/async-queue",
+
"@types/ws",
+
"@vladfrangu/async_event_emitter",
+
"discord-api-types",
+
"tslib",
+
"ws"
+
]
+
},
+
"@sapphire/async-queue@1.5.5": {
+
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="
+
},
+
"@sapphire/shapeshift@4.0.0": {
+
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
+
"dependencies": [
+
"fast-deep-equal",
+
"lodash"
+
]
+
},
+
"@sapphire/snowflake@3.5.3": {
+
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="
+
},
+
"@types/node@22.15.15": {
+
"integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
+
"dependencies": [
+
"undici-types"
+
]
+
},
+
"@types/ws@8.18.1": {
+
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+
"dependencies": [
+
"@types/node"
+
]
+
},
+
"@vladfrangu/async_event_emitter@2.4.7": {
+
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="
+
},
+
"discord-api-types@0.38.36": {
+
"integrity": "sha512-qrbUbjjwtyeBg5HsAlm1C859epfOyiLjPqAOzkdWlCNsZCWJrertnETF/NwM8H+waMFU58xGSc5eXUfXah+WTQ=="
+
},
+
"discord.js@14.25.1": {
+
"integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
+
"dependencies": [
+
"@discordjs/builders",
+
"@discordjs/collection@1.5.3",
+
"@discordjs/formatters",
+
"@discordjs/rest",
+
"@discordjs/util",
+
"@discordjs/ws",
+
"@sapphire/snowflake",
+
"discord-api-types",
+
"fast-deep-equal",
+
"lodash.snakecase",
+
"magic-bytes.js",
+
"tslib",
+
"undici"
+
]
+
},
+
"fast-deep-equal@3.1.3": {
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+
},
+
"lodash.snakecase@4.1.1": {
+
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
+
},
+
"lodash@4.17.21": {
+
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+
},
+
"magic-bytes.js@1.12.1": {
+
"integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="
+
},
+
"mime@4.1.0": {
+
"integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==",
+
"bin": true
+
},
+
"ts-mixer@6.0.4": {
+
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
+
},
+
"tslib@2.8.1": {
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+
},
+
"undici-types@6.21.0": {
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
+
},
+
"undici@6.21.3": {
+
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="
+
},
+
"ws@8.18.3": {
+
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="
+
}
+
},
+
"workspace": {
+
"dependencies": [
+
"jsr:@std/assert@1",
+
"npm:discord.js@^14.25.1",
+
"npm:mime@^4.1.0"
+
]
+
}
+
}
-19
package.json
···
-
{
-
"name": "discord",
-
"module": "index.ts",
-
"type": "module",
-
"private": true,
-
"scripts": {
-
"dev": "bun --watch src/index.ts",
-
"commands:sync": "bun src/sync.js"
-
},
-
"devDependencies": {
-
"@types/bun": "latest"
-
},
-
"peerDependencies": {
-
"typescript": "^5"
-
},
-
"dependencies": {
-
"discord.js": "^14.18.0"
-
}
-
}
+18 -23
src/commands/dong/create.ts
···
import {
-
Attachment,
+
AttachmentBuilder,
ChatInputCommandInteraction,
SlashCommandBuilder,
} from "discord.js";
-
import type { customClient } from "../..";
-
-
const download = async (file: Attachment): Promise<File> =>
-
new File(
-
[
-
await fetch(file.url).then((res) => {
-
return res.blob();
-
}),
-
],
-
file.name,
-
{
-
type: file.contentType ?? "application/octet-stream",
-
}
-
);
+
import type { customClient } from "../../index.ts";
+
import { createDong } from "../../lib/dong-io.ts";
+
import { download } from "../../lib/download.ts";
+
import { Buffer } from "node:buffer";
export const data = new SlashCommandBuilder()
.setName("create")
···
await interaction.deferReply();
-
let downloaded = {
+
const downloaded = {
image: await download(image),
audio: await download(audio),
};
-
console.log(downloaded);
-
await interaction.editReply(`Not implemented! Debug:
-
\`\`\`
-
filename: ${filename}
-
image: ${image.contentType} | ${image.url}
+
const dong = new File(
+
[await createDong(downloaded.image, downloaded.audio)],
+
filename,
+
{ type: "application/prs.vielle.dong" }
+
);
-
audio: ${audio.contentType} | ${audio.url}
-
\`\`\``);
+
await interaction.editReply({
+
files: [
+
new AttachmentBuilder(Buffer.from(await dong.arrayBuffer()), {
+
name: dong.name,
+
}),
+
],
+
});
};
+47 -7
src/commands/dong/open.ts
···
-
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
-
import type { customClient } from "../..";
+
import {
+
AttachmentBuilder,
+
ChatInputCommandInteraction,
+
MessageFlags,
+
SlashCommandBuilder,
+
} from "discord.js";
+
import type { customClient } from "../../index.ts";
+
import { download } from "../../lib/download.ts";
+
import { readDong } from "../../lib/dong-io.ts";
+
import { Mime } from "mime";
+
import standardTypes from "mime/types/standard.js";
+
import otherTypes from "mime/types/other.js";
+
import { Buffer } from "node:buffer";
+
+
const mime = new Mime(standardTypes, otherTypes);
+
mime.define({ "audio/mpeg": ["mp3"] });
export const data = new SlashCommandBuilder()
.setName("open")
.setDescription("Open a dong file!")
.addAttachmentOption((opt) =>
-
opt
-
.setName("dong")
-
.setDescription("The dong file")
-
.setRequired(true)
+
opt.setName("dong").setDescription("The dong file").setRequired(true)
);
export const execute = async (
interaction: ChatInputCommandInteraction & { client: customClient }
) => {
-
await interaction.reply("Not Implemented!");
+
const dong = interaction.options.getAttachment("dong", true);
+
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
+
+
const downloadedDong = await download(dong);
+
+
const output = await readDong(downloadedDong);
+
if (typeof output === "string") {
+
await interaction.editReply(output);
+
return;
+
}
+
const { image, audio } = output;
+
+
await interaction.editReply({
+
files: [
+
(() => {
+
const img = new AttachmentBuilder(Buffer.from(image.data.buffer), {
+
name: `${dong.name.match(/^.*(?=\.dong$)/gm)}.${mime.getExtension(
+
image.mime
+
)}`,
+
});
+
img.setSpoiler(true);
+
return img;
+
})(),
+
new AttachmentBuilder(Buffer.from(audio.data.buffer), {
+
name: `${dong.name.match(/^.*(?=\.dong$)/gm)}.${mime.getExtension(
+
audio.mime
+
)}`,
+
}),
+
],
+
});
};
+2 -5
src/commands/util/ping.ts
···
-
import {
-
ChatInputCommandInteraction,
-
SlashCommandBuilder,
-
} from "discord.js";
-
import type { customClient } from "../..";
+
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
+
import type { customClient } from "../../index.ts";
export const data = new SlashCommandBuilder()
.setName("ping")
+13 -5
src/index.ts
···
MessageFlags,
type Interaction,
} from "discord.js";
-
import { Glob } from "bun";
+
import { glob } from "node:fs/promises";
-
const token = process.env.token;
+
const token = Deno.env.get("TOKEN");
if (!token) throw new Error("Token required. Please fill in TOKEN in .env");
console.log("Token Valid!");
+
+
const __dirname = import.meta.dirname;
// client typing
export type customClient = Client & {
···
// setup commands
client.commands = new Collection();
-
const commandGlob = new Glob("**/*.ts");
-
for await (const file of commandGlob.scan("./src/commands")) {
-
const command = await import("./commands/" + file);
+
for await (const file of glob("src/commands/**/*.ts", {
+
exclude: ["node_modules"],
+
})) {
+
console.log(file);
+
const command = await import(`../${file}`);
// check command contains all required properties
if (
"data" in command &&
···
console.error(`No command ${interaction.commandName}`);
return;
}
+
+
console.log(
+
`Got command /${interaction.commandName} from @${interaction.user.username}`
+
);
try {
await command.execute(interaction);
+109
src/lib/dong-io.ts
···
+
declare global {
+
interface Uint8Array {
+
toBase64(): string;
+
}
+
}
+
+
const blobBytes = (blob: Blob) => {
+
if ("bytes" in blob) return blob.bytes();
+
return new Response(blob).arrayBuffer().then((buffer) => {
+
const uint = new Uint8Array(buffer);
+
return uint;
+
});
+
};
+
+
export const createDong = async (
+
image: File,
+
audio: File
+
): Promise<Blob | string> => {
+
if (
+
image.type === "" ||
+
!image.type.startsWith("image/") ||
+
audio.type === "" ||
+
!audio.type.startsWith("audio/")
+
)
+
return "Mime types invalid";
+
return new Blob([
+
// version
+
(() => {
+
const version = new Int8Array(new ArrayBuffer(2));
+
version[0] = 0xd0;
+
version[1] = 2;
+
return version;
+
})(),
+
// image type
+
image.type,
+
// 00 padding
+
new ArrayBuffer(256 - image.type.length),
+
// image size
+
(() => {
+
const value = new Uint32Array(1);
+
value[0] = image.size;
+
return value;
+
})(),
+
// audio type
+
audio.type,
+
// 00 padding
+
new ArrayBuffer(256 - audio.type.length),
+
// audio size
+
(() => {
+
const value = new Uint32Array(1);
+
value[0] = audio.size;
+
return value;
+
})(),
+
// image data
+
await blobBytes(image),
+
// audio data
+
await blobBytes(audio),
+
]);
+
};
+
+
export async function readDong(dongFile: File): Promise<
+
| {
+
image: { data: Uint8Array; mime: string };
+
audio: { data: Uint8Array; mime: string };
+
}
+
| string
+
> {
+
// get first 2 bytes and verify
+
const version = new Uint8Array(await blobBytes(dongFile.slice(0, 2)));
+
if (version[0] !== 0xd0 || version[1] !== 2) return "Invalid file";
+
+
// get next 256 bytes and get mime type
+
const imgMimeType: string | undefined = (
+
await dongFile.slice(2, 258).text()
+
).match(/[a-zA-Z0-9.]+\/[a-zA-Z0-9.]+/gm)?.[0];
+
if (!imgMimeType) return "Image mime type parse failed";
+
+
// get next 4 bytes and get image size
+
const imgSize = new Uint32Array(
+
(await blobBytes(dongFile.slice(258, 262))).buffer
+
)[0];
+
+
// get next 256 bytes and get mime type
+
const audMimeType: string | undefined = (
+
await dongFile.slice(262, 518).text()
+
).match(/[a-zA-Z0-9.]+\/[a-zA-Z0-9.]+/gm)?.[0];
+
if (!audMimeType) return "Audio mime type parse failed";
+
+
// get next 4 bytes and get image size
+
const audSize = new Uint32Array(
+
(await blobBytes(dongFile.slice(518, 522))).buffer
+
)[0];
+
+
const imageBytes = await blobBytes(dongFile.slice(522, 522 + imgSize));
+
const audioBytes = await blobBytes(
+
dongFile.slice(522 + imgSize, 522 + imgSize + audSize)
+
);
+
+
return {
+
image: {
+
mime: imgMimeType,
+
data: imageBytes,
+
},
+
audio: {
+
mime: audMimeType,
+
data: audioBytes,
+
},
+
};
+
}
+14
src/lib/download.ts
···
+
import type { Attachment } from "discord.js";
+
+
export const download = async (file: Attachment): Promise<File> =>
+
new File(
+
[
+
await fetch(file.url).then((res) => {
+
return res.blob();
+
}),
+
],
+
file.name,
+
{
+
type: file.contentType ?? "application/octet-stream",
+
}
+
);
+12 -8
src/sync.js
···
-
const { REST, Routes } = require("discord.js");
-
const { client: clientId, guild: guildId, token } = process.env;
-
const fs = require("node:fs");
-
const path = require("node:path");
+
import { REST, Routes } from "discord.js";
+
import fs from "node:fs";
+
import path from "node:path";
+
+
const clientId = Deno.env.get("CLIENT");
+
const token = Deno.env.get("TOKEN");
+
if (!clientId) throw "CLIENT not defined";
+
if (!token) throw "TOKEN not defined";
const commands = [];
// Grab all the command folders from the commands directory you created earlier
-
const foldersPath = path.join(__dirname, "commands");
+
const foldersPath = path.join(import.meta.dirname, "commands");
const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) {
···
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
-
const command = require(filePath);
+
const command = await import(filePath);
if ("data" in command && "execute" in command) {
commands.push(command.data.toJSON());
} else {
···
`Started refreshing ${commands.length} application (/) commands.`
);
-
// The put method is used to fully refresh all commands in the guild with the current set
-
const data = await rest.put(Routes.applicationGuildCommands(clientId, guildId), {
+
// The put method is used to fully refresh all commands with the current set
+
const data = await rest.put(Routes.applicationCommands(clientId), {
body: commands,
});
-27
tsconfig.json
···
-
{
-
"compilerOptions": {
-
// Enable latest features
-
"lib": ["ESNext", "DOM"],
-
"target": "ESNext",
-
"module": "ESNext",
-
"moduleDetection": "force",
-
"jsx": "react-jsx",
-
"allowJs": true,
-
-
// Bundler mode
-
"moduleResolution": "bundler",
-
"allowImportingTsExtensions": true,
-
"verbatimModuleSyntax": true,
-
"noEmit": true,
-
-
// Best practices
-
"strict": true,
-
"skipLibCheck": true,
-
"noFallthroughCasesInSwitch": true,
-
-
// Some stricter flags (disabled by default)
-
"noUnusedLocals": false,
-
"noUnusedParameters": false,
-
"noPropertyAccessFromIndexSignature": false
-
}
-
}