├── .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 | [![Banner](/assets/banner.png)](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