├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── assets
├── banner.png
├── compare
│ └── background.png
├── fonts
│ ├── Inter
│ │ ├── Inter_28pt-Black.ttf
│ │ ├── Inter_28pt-Bold.ttf
│ │ ├── Inter_28pt-Regular.ttf
│ │ └── Inter_28pt-SemiBold.ttf
│ ├── RefrigeratorDeluxe.otf
│ ├── RefrigeratorDeluxeBold.otf
│ └── leaderboard.ttf
├── heroes_bg
│ ├── 1011.png
│ ├── 1014.png
│ ├── 1015.png
│ ├── 1016.png
│ ├── 1017.png
│ ├── 1018.png
│ ├── 1020.png
│ ├── 1021.png
│ ├── 1022.png
│ ├── 1023.png
│ ├── 1024.png
│ ├── 1025.png
│ ├── 1026.png
│ ├── 1027.png
│ ├── 1029.png
│ ├── 1030.png
│ ├── 1031.png
│ ├── 1032.png
│ ├── 1033.png
│ ├── 1034.png
│ ├── 1035.png
│ ├── 1036.png
│ ├── 1037.png
│ ├── 1038.png
│ ├── 1039.png
│ ├── 1040.png
│ ├── 1041.png
│ ├── 1042.png
│ ├── 1043.png
│ ├── 1045.png
│ ├── 1046.png
│ ├── 1047.png
│ ├── 1048.png
│ ├── 1049.png
│ ├── 1050.png
│ ├── 1051.png
│ ├── 1052.png
│ └── 1053.png
├── json
│ └── maps.json
├── leaderboard
│ ├── blur_background.png
│ └── crowns
│ │ ├── first.png
│ │ ├── second.png
│ │ └── third.png
├── logo.jpg
├── match-history
│ └── background.png
├── placeholders
│ ├── hero_square.png
│ ├── hero_square.svg
│ ├── rank_icon.png
│ ├── rank_icon.svg
│ ├── user_icon.png
│ └── user_icon.svg
├── private-profile.png
├── profile
│ ├── background.png
│ ├── backgroundv2.png
│ ├── level_bg.png
│ └── profile_map.png
├── rank
│ └── background.png
└── ranks
│ ├── bronze.png
│ ├── celestial.png
│ ├── diamond.png
│ ├── eternity.png
│ ├── gold.png
│ ├── grandmaster.png
│ ├── invalid.png
│ ├── one_above_all.png
│ ├── platinum.png
│ └── silver.png
├── bun.lock
├── cache
└── .gitkeep
├── eslint.config.mjs
├── package.json
├── prisma
└── schema.prisma
├── seyfert.config.mts
├── src
├── commands
│ ├── account
│ │ ├── _.ts
│ │ ├── link.ts
│ │ └── unlink.ts
│ ├── game
│ │ ├── _.ts
│ │ ├── map.ts
│ │ └── patch-notes.ts
│ ├── help.ts
│ ├── hero
│ │ ├── _.ts
│ │ ├── about.ts
│ │ └── leaderboard.ts
│ ├── match
│ │ ├── _.ts
│ │ └── history.ts
│ ├── ping.ts
│ └── player
│ │ ├── _.ts
│ │ ├── compare.ts
│ │ ├── profile.ts
│ │ ├── rank.ts
│ │ ├── uid.ts
│ │ └── update.ts
├── events
│ ├── botReady.ts
│ └── messageCreate.ts
├── index.ts
├── lib
│ ├── constants.ts
│ └── managers
│ │ └── api.ts
├── locales
│ ├── en-US
│ │ ├── _.ts
│ │ ├── commands.ts
│ │ └── common.ts
│ └── es-419
│ │ ├── _.ts
│ │ ├── commands.ts
│ │ └── common.ts
├── middlewares
│ ├── cooldown.ts
│ └── index.ts
├── types
│ └── dtos
│ │ ├── APIError.ts
│ │ ├── FoundPlayerDTO.ts
│ │ ├── HeroDTO.ts
│ │ ├── HeroesDTO.ts
│ │ ├── LeaderboardPlayerHeroDTO.ts
│ │ ├── MapsDTO.ts
│ │ ├── MatchDTO.ts
│ │ ├── MatchHistoryDTO.ts
│ │ ├── PatchNotesDTO.ts
│ │ ├── PlayerDTO.ts
│ │ └── UpdateDTO.ts
└── utils
│ ├── env.ts
│ ├── functions
│ ├── capitalize.ts
│ ├── getEmoji.ts
│ ├── index.ts
│ ├── maps.ts
│ ├── rank-utils.ts
│ ├── scrapePatchNotes.ts
│ └── skins.ts
│ ├── images
│ ├── _.ts
│ ├── compare.ts
│ ├── leaderboard.ts
│ ├── match-history.ts
│ ├── profile.ts
│ ├── ranked.ts
│ └── utils.ts
│ ├── paginator.ts
│ └── types.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | BOT_TOKEN=
2 | API_KEY=one,two,....
3 | TRACKER=
4 | MARVELRIVALS=
5 | STICKY_API=
6 | STICKY_CDN=
7 | WEBHOOK_ID=
8 | WEBHOOK_TOKEN=
9 | TOPGG_TOKEN=
10 | DATABASE_URL=
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Caches
14 |
15 | .cache
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 |
19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
20 |
21 | # Runtime data
22 |
23 | pids
24 | _.pid
25 | _.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 |
30 | lib-cov
31 |
32 | # Coverage directory used by tools like istanbul
33 |
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 |
39 | .nyc_output
40 |
41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
42 |
43 | .grunt
44 |
45 | # Bower dependency directory (https://bower.io/)
46 |
47 | bower_components
48 |
49 | # node-waf configuration
50 |
51 | .lock-wscript
52 |
53 | # Compiled binary addons (https://nodejs.org/api/addons.html)
54 |
55 | build/Release
56 |
57 | # Dependency directories
58 |
59 | node_modules/
60 | jspm_packages/
61 |
62 | # Snowpack dependency directory (https://snowpack.dev/)
63 |
64 | web_modules/
65 |
66 | # TypeScript cache
67 |
68 | *.tsbuildinfo
69 |
70 | # Optional npm cache directory
71 |
72 | .npm
73 |
74 | # Optional eslint cache
75 |
76 | .eslintcache
77 |
78 | # Optional stylelint cache
79 |
80 | .stylelintcache
81 |
82 | # Microbundle cache
83 |
84 | .rpt2_cache/
85 | .rts2_cache_cjs/
86 | .rts2_cache_es/
87 | .rts2_cache_umd/
88 |
89 | # Optional REPL history
90 |
91 | .node_repl_history
92 |
93 | # Output of 'npm pack'
94 |
95 | *.tgz
96 |
97 | # Yarn Integrity file
98 |
99 | .yarn-integrity
100 |
101 | # dotenv environment variable files
102 |
103 | .env
104 | .env.development.local
105 | .env.test.local
106 | .env.production.local
107 | .env.local
108 |
109 | # parcel-bundler cache (https://parceljs.org/)
110 |
111 | .parcel-cache
112 |
113 | # Next.js build output
114 |
115 | .next
116 | out
117 |
118 | # Nuxt.js build / generate output
119 |
120 | .nuxt
121 | dist
122 |
123 | # Gatsby files
124 |
125 | # Comment in the public line in if your project uses Gatsby and not Next.js
126 |
127 | # https://nextjs.org/blog/next-9-1#public-directory-support
128 |
129 | # public
130 |
131 | # vuepress build output
132 |
133 | .vuepress/dist
134 |
135 | # vuepress v2.x temp and cache directory
136 |
137 | .temp
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 |
177 | cache/*
178 | prisma/client
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2025 Fabrizio Santana & MARCROCK22
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://discord.com/oauth2/authorize?client_id=1337677960546881587&permissions=563224831642688&integration_type=0&scope=bot)
2 |
3 | **Spider-Byte** is a powerful Discord bot designed to provide detailed stats, hero insights, and the latest updates for _Marvel Rivals_. Whether you're tracking your progress or learning more about your favorite heroes, Spider-Byte has you covered!
4 |
5 | 🚨 **Disclaimer:** Spider-Byte is **not affiliated** with the creators _NetEase, Inc._ This bot is a fan-made project for the community.
6 |
7 | ---
8 |
9 | ## 📌 Features
10 |
11 | ✅ **Player Stats** – Get in-depth performance statistics.
12 | ✅ **Hero Info** – Detailed hero abilities and tips.
13 | ✅ **Game Updates** – Stay informed with the latest news.
14 | ✅ **Match History** – Track past matches and analyze performance.
15 | ✅ **Leaderboards** – See how you rank against other players.
16 | ✅ **More Features Coming Soon!**
17 |
18 | ---
19 |
20 | ## 🗿 Invite the Bot
21 |
22 | Click [here](https://discord.com/oauth2/authorize?client_id=1337677960546881587&permissions=563224831642688&integration_type=0&scope=bot) to invite Spider-Byte to your server.
23 |
24 | ---
25 |
26 | ## 📢 Support & Community
27 |
28 | Join our [Discord Server](https://discord.gg/AcruVkyYHm) for support, feature requests, and discussions!
29 |
30 | ---
31 |
32 | ## 👨💻 Developers
33 |
34 | - **[@fbrz.sf](https://github.com/FabrizioCoder)**
35 | - **[@marcrock22](https://github.com/MARCROCK22)**
36 |
37 | ---
38 |
39 | ## 💪 Contributors
40 |
41 | - **[@simxnet](https://github.com/simxnet)** - designer
42 | - **[@simxnet](https://github.com/simxnet)** - frontend engineer
43 | - **[@simxnet](https://github.com/simxnet)** - rust expert
44 |
45 | ---
46 |
47 | ## 📜 License
48 |
49 | Spider-Byte is open-source and available under the **MIT License**.
50 |
51 | ---
52 |
53 | _Spider-Byte – "With great power comes great responsibility"_
54 |
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/banner.png
--------------------------------------------------------------------------------
/assets/compare/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/compare/background.png
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_28pt-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/fonts/Inter/Inter_28pt-Black.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_28pt-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/fonts/Inter/Inter_28pt-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_28pt-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/fonts/Inter/Inter_28pt-Regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_28pt-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/fonts/Inter/Inter_28pt-SemiBold.ttf
--------------------------------------------------------------------------------
/assets/fonts/RefrigeratorDeluxe.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/fonts/RefrigeratorDeluxe.otf
--------------------------------------------------------------------------------
/assets/fonts/RefrigeratorDeluxeBold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/fonts/RefrigeratorDeluxeBold.otf
--------------------------------------------------------------------------------
/assets/fonts/leaderboard.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/fonts/leaderboard.ttf
--------------------------------------------------------------------------------
/assets/heroes_bg/1011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1011.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1014.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1014.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1015.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1016.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1016.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1017.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1017.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1018.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1018.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1020.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1021.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1021.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1022.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1022.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1023.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1023.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1024.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1025.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1025.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1026.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1026.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1027.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1027.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1029.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1029.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1030.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1030.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1031.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1031.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1032.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1032.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1033.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1033.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1034.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1034.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1035.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1035.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1036.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1036.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1037.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1037.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1038.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1038.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1039.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1039.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1040.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1040.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1041.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1041.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1042.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1042.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1043.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1043.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1045.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1045.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1046.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1046.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1047.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1047.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1048.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1049.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1049.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1050.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1050.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1051.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1051.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1052.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1052.png
--------------------------------------------------------------------------------
/assets/heroes_bg/1053.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/heroes_bg/1053.png
--------------------------------------------------------------------------------
/assets/leaderboard/blur_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/leaderboard/blur_background.png
--------------------------------------------------------------------------------
/assets/leaderboard/crowns/first.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/leaderboard/crowns/first.png
--------------------------------------------------------------------------------
/assets/leaderboard/crowns/second.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/leaderboard/crowns/second.png
--------------------------------------------------------------------------------
/assets/leaderboard/crowns/third.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/leaderboard/crowns/third.png
--------------------------------------------------------------------------------
/assets/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/logo.jpg
--------------------------------------------------------------------------------
/assets/match-history/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/match-history/background.png
--------------------------------------------------------------------------------
/assets/placeholders/hero_square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/placeholders/hero_square.png
--------------------------------------------------------------------------------
/assets/placeholders/hero_square.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/placeholders/rank_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/placeholders/rank_icon.png
--------------------------------------------------------------------------------
/assets/placeholders/rank_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/placeholders/user_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/placeholders/user_icon.png
--------------------------------------------------------------------------------
/assets/placeholders/user_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/private-profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/private-profile.png
--------------------------------------------------------------------------------
/assets/profile/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/profile/background.png
--------------------------------------------------------------------------------
/assets/profile/backgroundv2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/profile/backgroundv2.png
--------------------------------------------------------------------------------
/assets/profile/level_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/profile/level_bg.png
--------------------------------------------------------------------------------
/assets/profile/profile_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/profile/profile_map.png
--------------------------------------------------------------------------------
/assets/rank/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/rank/background.png
--------------------------------------------------------------------------------
/assets/ranks/bronze.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/bronze.png
--------------------------------------------------------------------------------
/assets/ranks/celestial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/celestial.png
--------------------------------------------------------------------------------
/assets/ranks/diamond.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/diamond.png
--------------------------------------------------------------------------------
/assets/ranks/eternity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/eternity.png
--------------------------------------------------------------------------------
/assets/ranks/gold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/gold.png
--------------------------------------------------------------------------------
/assets/ranks/grandmaster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/grandmaster.png
--------------------------------------------------------------------------------
/assets/ranks/invalid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/invalid.png
--------------------------------------------------------------------------------
/assets/ranks/one_above_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/one_above_all.png
--------------------------------------------------------------------------------
/assets/ranks/platinum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/platinum.png
--------------------------------------------------------------------------------
/assets/ranks/silver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/assets/ranks/silver.png
--------------------------------------------------------------------------------
/cache/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabrizioCoder/Spider-Byte/153ac8f8093b5f3042fc23a4df4241986ce15414/cache/.gitkeep
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import theConfig from '@marcrock22/eslint';
2 | import { config } from 'typescript-eslint';
3 |
4 | export default config(
5 | theConfig,
6 | );
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spider-byte",
3 | "module": "./src/index.ts",
4 | "type": "module",
5 | "devDependencies": {
6 | "@marcrock22/eslint": "github:marcrock22/eslintcf",
7 | "@types/bun": "^1.2.15",
8 | "eslint": "^9.28.0",
9 | "rimraf": "^6.0.1",
10 | "ts-patch": "^3.3.0",
11 | "typescript-eslint": "^8.33.1",
12 | "typia": "^9.3.1"
13 | },
14 | "peerDependencies": {
15 | "typescript": "~5.8.3"
16 | },
17 | "dependencies": {
18 | "@napi-rs/canvas": "^0.1.70",
19 | "@prisma/client": "^6.9.0",
20 | "@redis/client": "^5.5.5",
21 | "@resvg/resvg-js": "^2.6.2",
22 | "@top-gg/sdk": "^3.1.6",
23 | "cheerio": "^1.0.0",
24 | "didyoumean2": "^7.0.4",
25 | "domhandler": "^5.0.3",
26 | "prisma": "^6.9.0",
27 | "satori": "^0.13.2",
28 | "satori-html": "^0.3.2",
29 | "seyfert": "^3.1.3-dev-15217195551.0"
30 | },
31 | "scripts": {
32 | "start": "bun run build && bun --env-file=.env ./dist/index.ts",
33 | "prepare": "ts-patch install",
34 | "build": "bunx rimraf dist && typia generate --input src --output dist --project tsconfig.json",
35 | "production": "bun install --frozen-lockfile && bunx prisma generate && bun run build && bun --env-file=.env ./dist/index.ts production"
36 | }
37 | }
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
6 |
7 | generator client {
8 | provider = "prisma-client-js"
9 | output = "client"
10 | }
11 |
12 | datasource db {
13 | provider = "mongodb"
14 | url = env("DATABASE_URL")
15 | }
16 |
17 | model User {
18 | id String @id @default(auto()) @map("_id") @db.ObjectId
19 | userID String @unique
20 | rivalsUUID String
21 | rivalsUsername String?
22 | createdAt DateTime @default(now())
23 | updatedAt DateTime @updatedAt
24 | }
25 |
--------------------------------------------------------------------------------
/seyfert.config.mts:
--------------------------------------------------------------------------------
1 | import { config } from 'seyfert';
2 |
3 | const BOT_TOKEN = Bun.env.BOT_TOKEN;
4 |
5 | if (!BOT_TOKEN?.trim()) {
6 | throw new Error('Bun.env.BOT_TOKEN is not a valid token');
7 | }
8 |
9 | export default config.bot({
10 | token: BOT_TOKEN,
11 | locations: {
12 | base: 'dist',
13 | commands: 'commands',
14 | langs: 'locales',
15 | events: 'events'
16 | },
17 | intents: ['GuildMessages']
18 | });
19 |
--------------------------------------------------------------------------------
/src/commands/account/_.ts:
--------------------------------------------------------------------------------
1 | import { AutoLoad, LocalesT, Declare, Command } from 'seyfert';
2 |
3 | @Declare({
4 | name: 'account',
5 | description: 'Manage your linked account',
6 | contexts: ['BotDM', 'Guild', 'PrivateChannel'],
7 | integrationTypes: ['GuildInstall', 'UserInstall'],
8 | props: {
9 | ratelimit: {
10 | time: 7_000,
11 | type: 'user'
12 | }
13 | }
14 | })
15 | @AutoLoad()
16 | @LocalesT('commands.account.name', 'commands.account.description')
17 | export default class AccountCommand extends Command { }
18 |
--------------------------------------------------------------------------------
/src/commands/account/link.ts:
--------------------------------------------------------------------------------
1 | import { type CommandContext, createStringOption, SubCommand, LocalesT, Declare, Options } from 'seyfert';
2 |
3 | const options = {
4 | 'name-or-id': createStringOption({
5 | description: 'Enter the player name or ID to identify the player.',
6 | locales: {
7 | name: 'commands.commonOptions.nameOrId.name',
8 | description: 'commands.commonOptions.nameOrId.description'
9 | },
10 | required: true
11 | })
12 | };
13 |
14 | @Declare({
15 | name: 'link',
16 | description: 'Link your game account to your Discord account.'
17 | })
18 | @LocalesT('commands.account.link.name', 'commands.account.link.description')
19 | @Options(options)
20 | export default class LinkCommand extends SubCommand {
21 | async run(ctx: CommandContext) {
22 | await ctx.deferReply();
23 |
24 | const nameOrId = ctx.options['name-or-id'];
25 | if (!nameOrId) {
26 | return ctx.editOrReply({
27 | content: ctx.t.commands.commonErrors.noNameOrId.get()
28 | });
29 | }
30 |
31 | const alreadyLinked = await ctx.client.prisma.user.findFirst({
32 | where: {
33 | userID: ctx.author.id
34 | }
35 | });
36 | if (alreadyLinked) {
37 | return ctx.editOrReply({
38 | content: ctx.t.commands.account.link.alreadyLinked.get()
39 | });
40 | }
41 |
42 | const player = await ctx.client.api.getPlayer(nameOrId);
43 |
44 | if (!player) {
45 | return ctx.editOrReply({
46 | content: ctx.t.commands.commonErrors.playerNotFound.get()
47 | });
48 | }
49 |
50 |
51 | await ctx.client.prisma.user.create({
52 | data: {
53 | userID: ctx.author.id,
54 | rivalsUUID: player.uid.toString(),
55 | rivalsUsername: player.name
56 | }
57 | });
58 | return ctx.editOrReply({
59 | content: ctx.t.commands.account.link.success(player.name, player.uid).get()
60 | });
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/commands/account/unlink.ts:
--------------------------------------------------------------------------------
1 | import { type CommandContext, SubCommand, LocalesT, Declare } from 'seyfert';
2 |
3 | @Declare({
4 | name: 'unlink',
5 | description: 'Unlink your game account from your Discord account.'
6 | })
7 | @LocalesT('commands.account.unlink.name', 'commands.account.unlink.description')
8 | export default class UnlinkCommand extends SubCommand {
9 | async run(ctx: CommandContext) {
10 | await ctx.deferReply();
11 |
12 | const linkedAccount = await ctx.client.prisma.user.findFirst({
13 | where: {
14 | userID: ctx.author.id
15 | }
16 | });
17 | if (!linkedAccount) {
18 | return ctx.editOrReply({
19 | content: ctx.t.commands.account.unlink.notLinked.get()
20 | });
21 | }
22 |
23 | await ctx.client.prisma.user.delete({
24 | where: {
25 | userID: ctx.author.id
26 | }
27 | });
28 | return ctx.editOrReply({
29 | content: ctx.t.commands.account.unlink.success(linkedAccount.rivalsUUID).get()
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/game/_.ts:
--------------------------------------------------------------------------------
1 | import { AutoLoad, LocalesT, Declare, Command } from 'seyfert';
2 |
3 | @Declare({
4 | name: 'game',
5 | description: 'Access game maps information and latest patch notes',
6 | contexts: ['BotDM', 'Guild', 'PrivateChannel'],
7 | integrationTypes: ['GuildInstall', 'UserInstall'],
8 | props: {
9 | ratelimit: {
10 | time: 7_000,
11 | type: 'user'
12 | }
13 | }
14 | })
15 | @AutoLoad()
16 | @LocalesT('commands.game.name', 'commands.game.description')
17 | export default class GameCommand extends Command { }
18 |
--------------------------------------------------------------------------------
/src/commands/game/map.ts:
--------------------------------------------------------------------------------
1 | import { type CommandContext, createStringOption, SubCommand, Formatter, LocalesT, Declare, Options, Embed } from 'seyfert';
2 | import didYouMean, { ReturnTypeEnums } from 'didyoumean2';
3 |
4 | import mapsJson from '../../../assets/json/maps.json';
5 |
6 | const options = {
7 | name: createStringOption({
8 | description: 'The name of the map',
9 | locales: {
10 | name: 'commands.game.map.options.name.name',
11 | description: 'commands.game.map.options.name.description'
12 | },
13 | required: true,
14 | async autocomplete(interaction) {
15 | const input = interaction.getInput();
16 | const maps = mapsJson.maps;
17 |
18 | const uniqueMaps = new Map();
19 |
20 | if (input.length) {
21 | const matches = didYouMean(
22 | input,
23 | maps.map((map) => map.name),
24 | {
25 | returnType: ReturnTypeEnums.ALL_SORTED_MATCHES,
26 | threshold: 0.1
27 | }
28 | ).slice(0, 25);
29 |
30 | for (const mapName of matches) {
31 | const matchingMaps = maps.filter((m) => m.full_name === mapName);
32 |
33 | matchingMaps.forEach((map) => {
34 | const displayName = matchingMaps.length > 1
35 | ? `${map.full_name} (${map.location})`
36 | : map.full_name;
37 |
38 | uniqueMaps.set(displayName, map.id);
39 | });
40 | }
41 | } else {
42 | maps.toSorted((a, b) => a.name.localeCompare(b.name))
43 | .slice(0, 25)
44 | .forEach((map) => {
45 | const sameNameMaps = maps.filter((m) => m.name === map.name);
46 | const displayName = sameNameMaps.length > 1
47 | ? `${map.full_name} (${map.location})`
48 | : map.full_name;
49 |
50 | uniqueMaps.set(displayName, map.id);
51 | });
52 | }
53 | const result = Array.from(uniqueMaps.entries()).map(([name, id]) => ({
54 | name,
55 | value: String(id)
56 | }));
57 |
58 | return interaction.respond(result.length
59 | ? result
60 | : maps.slice(0, 25).map((map) => ({
61 | name: map.full_name,
62 | value: map.id.toString()
63 | })));
64 | }
65 | })
66 | };
67 |
68 | @Declare({
69 | name: 'map',
70 | description: 'Get the information of a map'
71 | })
72 | @LocalesT('commands.game.map.name', 'commands.game.map.description')
73 | @Options(options)
74 | export default class MapCommand extends SubCommand {
75 | async run(ctx: CommandContext) {
76 | await ctx.deferReply();
77 |
78 | const mapId = parseInt(ctx.options.name);
79 |
80 | const map = mapsJson.maps.find((m) => m.id === mapId) ?? null;
81 |
82 | if (!map) {
83 | return ctx.editOrReply({
84 | content: ctx.t.commands.game.map.notFound.get()
85 | });
86 | }
87 |
88 | const mapEmbed = new Embed()
89 | .setTitle(map.full_name)
90 | .setDescription(map.description)
91 | .setColor('Blurple')
92 | .setImage(ctx.client.api.buildImage(map.images[2].replace('/rivals', '')))
93 | .addFields([
94 | {
95 | name: Formatter.underline('Game Mode'),
96 | value: map.game_mode,
97 | inline: true
98 | },
99 | // {
100 | // name: Formatter.underline('Competitiveness'),
101 | // value: map.is_competitve
102 | // ? 'Competitive'
103 | // : 'Casual',
104 | // inline: true
105 | // },
106 | {
107 | name: Formatter.underline('Location'),
108 | value: map.location,
109 | inline: true
110 | }
111 | ]);
112 | if (map.video) {
113 | mapEmbed.setURL(map.video);
114 | }
115 | if (map.sub_map.id) {
116 | mapEmbed.addFields({
117 | name: Formatter.underline('Sub Map'),
118 | value: `* ${map.sub_map.id} - ${map.sub_map.name ?? 'N/A'}`,
119 | inline: true
120 | });
121 | }
122 |
123 | return ctx.editOrReply({ embeds: [mapEmbed] });
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/commands/game/patch-notes.ts:
--------------------------------------------------------------------------------
1 | import { type CommandContext, AttachmentBuilder, SubCommand, LocalesT, Declare } from 'seyfert';
2 | import { MessageFlags } from 'seyfert/lib/types';
3 |
4 | import { scrapePatchNotes } from '../../utils/functions/scrapePatchNotes';
5 | import { callbackPaginator } from '../../utils/paginator';
6 |
7 | @Declare({
8 | name: 'patch-notes',
9 | description: 'Get the latest patch notes or patch notes for a specific id'
10 | })
11 | @LocalesT('commands.game.patchNotes.name', 'commands.game.patchNotes.description')
12 | export default class RankCommand extends SubCommand {
13 | async run(ctx: CommandContext) {
14 | await ctx.deferReply();
15 | const patchNotesData = await scrapePatchNotes();
16 | if (!patchNotesData) {
17 | return ctx.editOrReply({
18 | content: ctx.t.commands.game.patchNotes.noPatchNotes.get()
19 | });
20 | }
21 |
22 | const { title, date, content, metadata } = patchNotesData;
23 | const header = `# [${title}](<${metadata.url}>)\nRelease Date: ${date}\n\n`;
24 | const img = new AttachmentBuilder()
25 | .setFile('url', patchNotesData.metadata.imageUrl)
26 | .setName('patch-notes.png');
27 |
28 | const fullContent = content.join('\n');
29 | if (fullContent.length + header.length <= 2_000) {
30 | return ctx.editOrReply({
31 | content: header + fullContent,
32 | flags: MessageFlags.SuppressEmbeds,
33 | files: [img]
34 | });
35 | }
36 |
37 | const chunks: string[] = [];
38 | let currentChunk = '';
39 |
40 | for (const line of content) {
41 | if (currentChunk.length + line.length + 1 > 1_750) {
42 | chunks.push(currentChunk);
43 | currentChunk = line;
44 | } else {
45 | currentChunk += (currentChunk
46 | ? '\n'
47 | : '') + line;
48 | }
49 | }
50 | if (currentChunk) {
51 | chunks.push(currentChunk);
52 | }
53 |
54 | await ctx.editOrReply({
55 | content: `${header}${chunks[0]}`,
56 | flags: MessageFlags.SuppressEmbeds,
57 | files: [img]
58 | });
59 |
60 | await callbackPaginator(ctx, chunks, {
61 | callback: (chunk) => ({
62 | content: `${header}${chunk[0]}`,
63 | flags: MessageFlags.SuppressEmbeds,
64 | files: [img]
65 | }),
66 | pageSize: 1
67 | });
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/commands/help.ts:
--------------------------------------------------------------------------------
1 | import type { CommandContext, CommandOption, SubCommand } from 'seyfert';
2 |
3 | import { createStringOption, Formatter, LocalesT, Command, Declare, Options, Embed } from 'seyfert';
4 |
5 | const options = {
6 | command: createStringOption({
7 | description: 'Specific command to get help for',
8 | locales: {
9 | name: 'commands.help.options.command.name',
10 | description: 'commands.help.options.command.description'
11 | },
12 | required: false,
13 | autocomplete: (interaction) => {
14 | const focused = interaction.getInput();
15 | const commands = interaction.client.commands.values;
16 |
17 | const matchedCommands = focused
18 | ? commands.filter((cmd) => cmd.name.toLowerCase().includes(focused.toLowerCase()))
19 | : commands;
20 |
21 | return interaction.respond(
22 | matchedCommands
23 | .map((cmd) => ({
24 | name: `${cmd.name} - ${cmd.description.slice(0, 50)}${cmd.description.length > 50
25 | ? '...'
26 | : ''}`,
27 | value: cmd.name
28 | }))
29 | .slice(0, 25)
30 | );
31 | }
32 | })
33 | };
34 |
35 | @Declare({
36 | name: 'help',
37 | description: 'Display information about available commands',
38 | contexts: ['BotDM', 'Guild', 'PrivateChannel'],
39 | integrationTypes: ['GuildInstall', 'UserInstall'],
40 | props: {
41 | ratelimit: {
42 | time: 10_000,
43 | type: 'user'
44 | }
45 | }
46 | })
47 | @LocalesT('commands.help.name', 'commands.help.description')
48 | @Options(options)
49 | export default class HelpCommand extends Command {
50 | async run(ctx: CommandContext) {
51 | const commandName = ctx.options.command;
52 |
53 | if (commandName) {
54 | return this.showCommandDetails(ctx, commandName);
55 | }
56 |
57 | return this.showCommandList(ctx);
58 | }
59 |
60 | private async showCommandList(ctx: CommandContext) {
61 | const commands = ctx.client.commands.values;
62 |
63 | const embed = new Embed(ctx.t.commands.help.embed.get());
64 |
65 | const commandList = commands
66 | .map((cmd) => `\`/${cmd.name}\` - ${cmd.description}`)
67 | .join('\n');
68 |
69 | embed.addFields({
70 | name: ctx.t.commands.help.fields.allCommands.get(),
71 | value: commandList
72 | });
73 |
74 | return ctx.editOrReply({ embeds: [embed] });
75 | }
76 |
77 | private async showCommandDetails(ctx: CommandContext, commandName: string) {
78 | const command = ctx.client.commands.values.find(
79 | (cmd) => cmd.name.toLowerCase() === commandName.toLowerCase()
80 | ) as undefined | Command;
81 |
82 | if (!command) {
83 | return ctx.editOrReply({
84 | content: ctx.t.commands.help.noCommandFound.get()
85 | });
86 | }
87 |
88 | const commandDetailsEmbed = new Embed(ctx.t.commands.help.commandDetailsEmbed(commandName).get());
89 |
90 |
91 | const subcommands = command.options?.filter((opt) => opt.type === 1 || opt.type === 2) as SubCommand[] | undefined;
92 | if (subcommands?.length) {
93 | const subcommandsField = subcommands
94 | .map((sub) => `**${sub.name}**: ${sub.description_localizations?.[
95 | ctx.interaction.locale
96 | ] || sub.description}`)
97 | .join('\n');
98 |
99 | commandDetailsEmbed.addFields({
100 | name: ctx.t.commands.help.fields.subcommands.get(),
101 | value: subcommandsField
102 | });
103 | }
104 | const opts = command.options?.filter((opt) => opt.type !== 1 && opt.type !== 2) as CommandOption[] | undefined;
105 | if (opts?.length) {
106 | const optionsField = opts
107 | .map((opt) => `**${opt.name}${opt.required
108 | ? '(Req)'
109 | : ''}**: ${opt.description_localizations?.[ctx.interaction.locale] || opt.description}`)
110 | .join('\n');
111 | commandDetailsEmbed.addFields({
112 | name: ctx.t.commands.help.fields.options.get(),
113 | value: optionsField
114 | });
115 | }
116 |
117 | const cooldown = command.props.ratelimit;
118 | if (cooldown) {
119 | commandDetailsEmbed.addFields({
120 | name: ctx.t.commands.help.fields.cooldown.get(),
121 | value: `${Formatter.inlineCode(String(cooldown.time / 1_000))} seconds per ${Formatter.inlineCode(cooldown.type)}`
122 | });
123 | }
124 |
125 | return ctx.editOrReply({ embeds: [commandDetailsEmbed] });
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/src/commands/hero/_.ts:
--------------------------------------------------------------------------------
1 | import { AutoLoad, LocalesT, Declare, Command } from 'seyfert';
2 |
3 | @Declare({
4 | name: 'hero',
5 | description: 'Access hero profiles, abilities, stats, and competitive rankings',
6 | contexts: ['BotDM', 'Guild', 'PrivateChannel'],
7 | integrationTypes: ['GuildInstall', 'UserInstall'],
8 | props: {
9 | ratelimit: {
10 | time: 10_000,
11 | type: 'user'
12 | }
13 | }
14 | })
15 | @AutoLoad()
16 | @LocalesT('commands.hero.name', 'commands.hero.description')
17 | export default class HeroCommand extends Command { }
18 |
--------------------------------------------------------------------------------
/src/commands/hero/about.ts:
--------------------------------------------------------------------------------
1 | import type { SelectMenuInteraction, ButtonInteraction } from 'seyfert';
2 | import type { ColorResolvable, MakeRequired } from 'seyfert/lib/common';
3 |
4 | import {
5 | createStringOption,
6 | StringSelectOption,
7 | StringSelectMenu,
8 | SubCommand,
9 | ActionRow,
10 | Formatter,
11 | LocalesT,
12 | Declare,
13 | Options,
14 | Embed
15 | } from 'seyfert';
16 | import { type CommandContext, type OKFunction, Button } from 'seyfert';
17 | import { MessageFlags, ButtonStyle } from 'seyfert/lib/types';
18 | import didYouMean, { ReturnTypeEnums } from 'didyoumean2';
19 |
20 | import type {
21 | HeroesDTO,
22 | Ability
23 | } from '../../types/dtos/HeroesDTO';
24 |
25 | import { parseNameForRivalSkins } from '../../utils/functions/skins';
26 | import { capitalize } from '../../utils/functions/capitalize';
27 | import { getHeroHistoryLink } from '../../utils/images/_';
28 | import { STICKY_CDN_DOMAIN } from '../../utils/env';
29 | import { Role } from '../../types/dtos/HeroesDTO';
30 |
31 | const colorPerRole = {
32 | [Role.Duelist]: '#FF4500',
33 | [Role.Strategist]: '#1E90FF',
34 | [Role.Vanguard]: '#8B0000'
35 | };
36 |
37 | const options = {
38 | name: createStringOption({
39 | description: 'The hero you want to get information about',
40 | async autocomplete(interaction) {
41 | const input = interaction.getInput();
42 | const heroes = await interaction.client.api.getHeroes();
43 | const result = (input.length
44 | ? didYouMean(
45 | interaction.getInput(),
46 | heroes.map((hero) => hero.name),
47 | {
48 | returnType: ReturnTypeEnums.ALL_SORTED_MATCHES,
49 | threshold: 0.1
50 | }
51 | ).slice(0, 25)
52 | : heroes.toSorted((a, b) => {
53 | if (a.name < b.name) {
54 | return -1;
55 | }
56 | if (a.name > b.name) {
57 | return 1;
58 | }
59 | return 0;
60 | }).slice(0, 25).map((
61 | hero
62 | ) => hero.name)).map((heroName) => ({
63 | name: heroName,
64 | value: heroName
65 | }));
66 |
67 | if (result.length) {
68 | return interaction.respond(
69 | result
70 | );
71 | }
72 |
73 | return interaction.respond(heroes.slice(0, 25).map((hero) => ({
74 | name: hero.name,
75 | value: hero.name
76 | })));
77 |
78 | },
79 | async value({ value, context: ctx }, ok: OKFunction, fail) {
80 | const hero = (await ctx.client.api.getHeroes()).find(
81 | (h) => h.name === value || h.id.toString() === value
82 | );
83 | if (!hero) {
84 | fail(ctx.t.commands.hero.about.notFound(Formatter.inlineCode(value)).get());
85 | return;
86 | }
87 |
88 | ok(hero);
89 | },
90 | required: true,
91 | locales: {
92 | name: 'commands.hero.about.options.name.name',
93 | description: 'commands.hero.about.options.name.description'
94 | }
95 | })
96 | };
97 |
98 | @Declare({
99 | name: 'about',
100 | description: 'Get detailed info about a hero, including abilities and stats.'
101 | })
102 | @LocalesT('commands.hero.about.name', 'commands.hero.about.description')
103 | @Options(options)
104 | export default class About extends SubCommand {
105 | async run(ctx: CommandContext) {
106 | const hero = ctx.options.name;
107 | const heroMoreInfo = await ctx.client.api.getHero(String(hero.id));
108 |
109 | if (!heroMoreInfo) {
110 | return ctx.editOrReply({
111 | content: ctx.t.commands.hero.about.notFound(Formatter.inlineCode(hero.name)).get()
112 | });
113 | }
114 |
115 | const baseEmbed = new Embed().setColor(
116 | colorPerRole[capitalize(hero.role) as Role] as ColorResolvable
117 | );
118 | const heroNameParsed = parseNameForRivalSkins(hero.name);
119 | baseEmbed.setThumbnail(`${STICKY_CDN_DOMAIN}/Content/Marvel_LQ/UI/Textures/HeroPortrait/SelectHero/img_selecthero_${hero.id}001.png`);
120 | baseEmbed.setAuthor({
121 | name: `${hero.real_name} (${heroNameParsed})`,
122 | iconUrl:
123 | `${STICKY_CDN_DOMAIN}/Content/Marvel_LQ/UI/Textures/Item/Kill/item_kill_2${hero.id}13.png`
124 | });
125 |
126 | baseEmbed.setDescription(hero.bio)
127 | .setImage(
128 | getHeroHistoryLink(hero.id)
129 | );
130 | baseEmbed.setFooter({
131 | text: capitalize(hero.role)
132 | });
133 |
134 | const heroEmbed = new Embed(baseEmbed.toJSON());
135 | const loreEmbed = new Embed(baseEmbed.toJSON());
136 | loreEmbed.setDescription(hero.lore);
137 |
138 | const message = await ctx.editOrReply(
139 | {
140 | embeds: [heroEmbed],
141 | components: [this.generateRows('hero')]
142 | },
143 | true
144 | );
145 |
146 | const collector = message.createComponentCollector({
147 | idle: 60 * 60 * 1e3,
148 | filter(interaction) {
149 | return interaction.user.id === ctx.author.id;
150 | }
151 | });
152 |
153 | collector.run(
154 | 'hero',
155 | (interaction) => interaction.update({
156 | embeds: [heroEmbed],
157 | components: [this.generateRows('hero')]
158 | })
159 | );
160 |
161 | collector.run(
162 | 'lore',
163 | (interaction) => interaction.update({
164 | embeds: [loreEmbed],
165 | components: [this.generateRows('lore')]
166 | })
167 | );
168 |
169 | collector.run(
170 | 'back',
171 | (interaction) => interaction.update({
172 | embeds: [heroEmbed],
173 | components: [this.generateRows('hero')]
174 | })
175 | );
176 |
177 | collector.run(
178 | 'combo',
179 | async (interaction) => {
180 | await interaction.deferReply(MessageFlags.Ephemeral);
181 | await interaction.editOrReply({
182 | content: `${STICKY_CDN_DOMAIN}/Content/Marvel/Movies_HeroSkill/Windows/En-US/${hero.id}/${hero.id}0010/${hero.id}_Combo_High.mp4`,
183 | flags: MessageFlags.Ephemeral
184 | });
185 | }
186 | );
187 |
188 | collector.run('abilities', async (interaction) => interaction.update({
189 | embeds: [interaction.message.embeds[0]],
190 | components: this.generateActionRowSelectMenu(hero.abilities)
191 | }));
192 |
193 | collector.run(/video_[0-9]{1,}/, async (interaction) => {
194 | const abilityID = interaction.customId.slice(6);
195 | await interaction.deferReply(MessageFlags.Ephemeral);
196 | await interaction.editOrReply({
197 | content: `${STICKY_CDN_DOMAIN}/Content/Marvel/Movies_HeroSkill/Windows/En-US/${hero.id}/${hero.id}0010/${abilityID}_High.mp4`,
198 | flags: MessageFlags.Ephemeral
199 | });
200 | });
201 |
202 | collector.run(/./, async (interaction) => {
203 | const abilityId = interaction.values.at(0);
204 | if (!abilityId) {
205 | return;
206 | }
207 |
208 | const abilityData = heroMoreInfo.abilities.find((a) => a.id === Number(abilityId));
209 | const abilityData2 = hero.abilities.find((a) => a.id === Number(abilityId));
210 | if (!abilityData || !abilityData2) {
211 | return;
212 | }
213 |
214 | const abilityEmbed = new Embed(baseEmbed.toJSON());
215 | const description: string[] = [];
216 | if (abilityData.description) {
217 | description.push(
218 | abilityData.description.replaceAll(
219 | /(<[a-z]{1,}>)|(<\/>)/gmi,
220 | ''
221 | )
222 | );
223 | }
224 |
225 | for (const field in abilityData.additional_fields) {
226 | const fieldContent = abilityData
227 | .additional_fields[field];
228 | if (!fieldContent) {
229 | continue;
230 | }
231 | description.push(`**${field}**: ${fieldContent}`);
232 | }
233 |
234 | abilityEmbed
235 | .setTitle(`${abilityData.name || abilityData2.name || 'Passive'} (${abilityData.type})`);
236 | abilityEmbed.setThumbnail(
237 | `${STICKY_CDN_DOMAIN}/Content/Marvel_LQ/UI/Textures/Ability/${hero.id}/icon_${abilityData.id}.png`
238 | );
239 | abilityEmbed.setDescription(description.join('\n'));
240 | await interaction.update({
241 | embeds: [abilityEmbed],
242 | components: this.generateActionRowSelectMenu(
243 | hero.abilities,
244 | abilityData as unknown as Omit
245 | )
246 | });
247 | });
248 | }
249 |
250 | onBeforeOptions(ctx: CommandContext) {
251 | return ctx.deferReply();
252 | }
253 |
254 | generateRows(clicked: 'hero' | 'lore') {
255 | const row = new ActionRow();
256 |
257 | const heroButton = new Button()
258 | .setLabel('Hero')
259 | .setStyle(ButtonStyle.Primary)
260 | .setCustomId('hero')
261 | .setDisabled(clicked === 'hero');
262 |
263 | const loreButton = new Button()
264 | .setLabel('Lore')
265 | .setStyle(ButtonStyle.Primary)
266 | .setCustomId('lore')
267 | .setDisabled(clicked === 'lore');
268 |
269 | const abilitiesButton = new Button()
270 | .setLabel('Abilities')
271 | .setStyle(ButtonStyle.Primary)
272 | .setCustomId('abilities');
273 |
274 | return row.addComponents(heroButton, loreButton, abilitiesButton);
275 | }
276 |
277 | generateActionRowSelectMenu(
278 | abilities: Ability[],
279 | selectedAbility?: Omit
280 | ): ActionRow[] {
281 | const selectMenuRow = new ActionRow();
282 | const buttonRow = new ActionRow