├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── blade.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── dataSources.xml ├── laracord.iml ├── laravel-idea-personal.xml ├── laravel-idea.xml ├── material_theme_project_new.xml ├── modules.xml ├── php.xml └── vcs.xml ├── .prettierrc ├── LICENSE.md ├── README.md ├── app ├── Actions │ ├── Mentors │ │ ├── Help │ │ │ ├── MentorHelpAction.php │ │ │ └── MentorHelpException.php │ │ ├── Join │ │ │ ├── MentorJoinAction.php │ │ │ └── MentorJoinException.php │ │ ├── MentorCommandInterface.php │ │ ├── MentorCommandsEnum.php │ │ └── MentorDTO.php │ └── Teams │ │ ├── Join │ │ ├── JoinTeamAction.php │ │ ├── JoinTeamDTO.php │ │ └── JoinTeamException.php │ │ └── Spawn │ │ └── SpawnTeamAction.php ├── Bot.php ├── Commands │ └── SpawnRoomsCommand.php ├── Console │ └── Commands │ │ └── LoadHackathonCommand.php ├── Enums │ ├── TeamNicheEnum.php │ └── TeamRoleEnum.php ├── Events │ └── GuildJoinEvent.php ├── Exceptions │ └── CommandException.php ├── Models │ ├── Guild.php │ ├── Mentor │ │ └── Mentor.php │ ├── Team │ │ ├── Member.php │ │ └── Team.php │ └── User.php ├── Providers │ └── BotServiceProvider.php └── SlashCommands │ ├── JoinTeamCommand.php │ ├── MentorCommand.php │ ├── OverviewCommand.php │ └── PingCommand.php ├── artisan ├── bootstrap └── app.php ├── box.json ├── composer.json ├── composer.lock ├── config ├── app.php ├── bot.php ├── database.php └── discord.php └── database ├── migrations ├── 0001_01_01_000000_create_users_table.php ├── 0002_01_01_000000_create_personal_access_tokens_table.php ├── 2024_05_15_154342_create_teams_table.php ├── 2024_05_15_154840_create_members_table.php ├── 2024_05_21_234316_create_mentors_table.php └── 2024_05_22_193613_create_guilds_table.php └── seeders ├── DatabaseSeeder.php ├── GuildsSeeder.php └── TeamsSeeder.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | quote_type = single 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.php] 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laracord 2 | APP_ENV=development 3 | 4 | DISCORD_TOKEN= 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | /.github export-ignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /builds 3 | /cache 4 | *.sqlite 5 | /database/database.sqlite 6 | 7 | .env 8 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/blade.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 16 | 17 | 26 | 27 | 30 | 31 | 38 | 39 | 46 | 47 | 54 | 55 | 60 | 61 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sqlite.xerial 6 | true 7 | org.sqlite.JDBC 8 | jdbc:sqlite:$PROJECT_DIR$/database/database.sqlite 9 | $ProjectFileDir$ 10 | 11 | 12 | mysql.8 13 | true 14 | com.mysql.cj.jdbc.Driver 15 | jdbc:mysql://localhost:3306 16 | $ProjectFileDir$ 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/laracord.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /.idea/laravel-idea-personal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/laravel-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 19 | -------------------------------------------------------------------------------- /.idea/material_theme_project_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 147 | 148 | 150 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Brandon Nifong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Hacka Server 2 | 3 | * 4 | -------------------------------------------------------------------------------- /app/Actions/Mentors/Help/MentorHelpAction.php: -------------------------------------------------------------------------------- 1 | sendHelpRequest($dto); 20 | 21 | return $this->respondWithMessage(); 22 | } 23 | 24 | 25 | private function sendHelpRequest(MentorDTO $dto) 26 | { 27 | $server = app('bot')->discord(); 28 | $guild = $server 29 | ->guilds 30 | ->get('id', config('bot.main_guild')); 31 | 32 | $channel = $guild 33 | ->channels 34 | ->find(fn($channel) => str_contains($channel->name, 'pedidos-de-ajuda')); 35 | 36 | $member = Member::where('discord_id', $dto->member->id)->first(); 37 | if (!$member) { 38 | throw MentorHelpException::notRegistered(); 39 | } 40 | 41 | 42 | $mentorType = $dto->args->pull('tipo-mentoria')->value; 43 | if ($mentorType === 'outro') { 44 | $discordId = "1241470842462928987"; 45 | } else { 46 | $discordId = TeamRoleEnum::from($mentorType)->getDiscordId(); 47 | } 48 | 49 | $mentorTag = sprintf('<@&%s>', $discordId); 50 | $channelRedirect = sprintf('<#%s>', $member->team->channels_ids[1]); 51 | dump($channelRedirect); 52 | 53 | $messageBuilder = Message::make(null) 54 | ->title('Pedido de Mentoria') 55 | ->content('**Contexto**: ' . $dto->args->pull('contexto')->value) 56 | ->field('Time', $member->team_id) 57 | ->field('Link Canal', $channelRedirect) 58 | ->field('Mentor Requisitado', $mentorTag) 59 | ->field('Mentorado', '<@' . $dto->member->id . '>') 60 | ->info() 61 | ->timestamp(); 62 | 63 | /** @var \Discord\Parts\Channel\Message $message */ 64 | $message = await($channel->sendMessage($messageBuilder->build())); 65 | 66 | // emotion eyes 67 | await($message->react('✅')); 68 | // emotion check 69 | await($message->react('👀')); 70 | } 71 | 72 | public function respondWithMessage(): MessageBuilder 73 | { 74 | return Message::make(app('bot')) 75 | ->content('Você requisitou a ajuda de um mentor! Em breve alguém te contatará via chat ou irá entrar na sua sala de voz.') 76 | ->build(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/Actions/Mentors/Help/MentorHelpException.php: -------------------------------------------------------------------------------- 1 | where('email', $dto->args->pull('email')->value) 18 | ->orWhere('provider_id', $dto->member->id) 19 | ->first(); 20 | 21 | if (!$mentor) { 22 | throw MentorJoinException::notFound(); 23 | } 24 | 25 | if ($mentor->accepted_at) { 26 | throw MentorJoinException::alreadyAccepted(); 27 | } 28 | 29 | $mentor->acceptInvite($dto); 30 | $this->addRole($dto); 31 | 32 | return $this->respondWithMessage(); 33 | } 34 | 35 | private function addRole(MentorDTO $dto): void 36 | { 37 | 38 | $alreadyHasARole = $dto->member->roles->find( 39 | fn($role) => $role->name === 'Pessoa Mentora' 40 | ); 41 | 42 | if ($alreadyHasARole) { 43 | return; 44 | } 45 | 46 | 47 | $mentorRole = $dto->guild->roles->find( 48 | fn($role) => $role->name === 'Pessoa Mentora' 49 | ); 50 | await($dto->member->addRole($mentorRole)); 51 | } 52 | 53 | private function respondWithMessage(): MessageBuilder 54 | { 55 | return Message::make(app('bot')) 56 | ->content('You have requested help from a mentor!') 57 | ->build(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Actions/Mentors/Join/MentorJoinException.php: -------------------------------------------------------------------------------- 1 | app(MentorJoinAction::class), 18 | self::Help => app(MentorHelpAction::class) 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Actions/Mentors/MentorDTO.php: -------------------------------------------------------------------------------- 1 | guild, 24 | member: $interaction->member, 25 | args: $interaction->data->options->first()->options 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Actions/Teams/Join/JoinTeamAction.php: -------------------------------------------------------------------------------- 1 | 'geral', 16 | 'category' => Channel::TYPE_TEXT 17 | ], 18 | // [ 19 | // 'name' => 'links-uteis', 20 | // 'category' => Channel::TYPE_TEXT 21 | // ], 22 | [ 23 | 'name' => 'Voz', 24 | 'category' => Channel::TYPE_VOICE 25 | ], 26 | ]; 27 | 28 | public function __construct( 29 | private readonly Team $team, 30 | private readonly TeamMember $teamMember 31 | ) 32 | { 33 | } 34 | 35 | public function handle(JoinTeamDTO $dto): Team 36 | { 37 | $memberIsAlreadyInATeam = $this->teamMember->alreadyJoinedATeam($dto->member->id); 38 | if ($memberIsAlreadyInATeam) { 39 | $inviteUrl = $memberIsAlreadyInATeam->team->guild->invite_url; 40 | throw JoinTeamException::alreadyInATeam($inviteUrl); 41 | } 42 | 43 | $team = $this->team->findByOwnerEmail($dto->teamKey); 44 | 45 | if (!$team) { 46 | throw JoinTeamException::teamCodeNotExists($dto); 47 | } 48 | 49 | 50 | if (!$team->guild_id) { 51 | $this->setGuildToTeam($team); 52 | } 53 | 54 | $team->addMember($dto); 55 | $this->manageRoles($dto); 56 | 57 | return $team; 58 | } 59 | 60 | private function manageRoles(JoinTeamDTO $dto): void 61 | { 62 | $teamlessRole = $dto->member->guild->roles->find(fn($role) => $role->name === 'Sem Time'); 63 | $teamedRole = $dto->member->guild->roles->find(fn($role) => $role->name === 'Em Time'); 64 | 65 | $hasTeamRole = $dto->member->roles->find(fn($role) => $role->name === 'Em Time'); 66 | if (!$hasTeamRole) { 67 | await($dto->member->addRole($teamedRole)); 68 | } 69 | 70 | $hasTeamlessRole = $dto->member->roles->find(fn($role) => $role->name === 'Sem Time'); 71 | if ($hasTeamlessRole) { 72 | await($dto->member->removeRole($teamlessRole)); 73 | } 74 | 75 | } 76 | 77 | private function setGuildToTeam(Team $team): void 78 | { 79 | $guild = Guild::query() 80 | ->where('main_server', false) 81 | ->where('teams_count', '<', config('bot.teamsPerGuild')) 82 | ->first(); 83 | 84 | $team->update(['guild_id' => $guild->provider_id]); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /app/Actions/Teams/Join/JoinTeamDTO.php: -------------------------------------------------------------------------------- 1 | member, 25 | selectedMemberRoleType: TeamRoleEnum::from($interaction->data->options->pull('area')->value), 26 | teamKey: $interaction->data->options->pull('chave')->value, 27 | githubUsername: $interaction->data->options->pull('github')?->value, 28 | ); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Actions/Teams/Join/JoinTeamException.php: -------------------------------------------------------------------------------- 1 | teamKey)); 22 | } 23 | 24 | public static function teamAlreadyFull(): self 25 | { 26 | return new self('Esse time já está cheio!'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Actions/Teams/Spawn/SpawnTeamAction.php: -------------------------------------------------------------------------------- 1 | 'geral', 17 | 'category' => Channel::TYPE_TEXT 18 | ], 19 | // [ 20 | // 'name' => 'links-uteis', 21 | // 'category' => Channel::TYPE_TEXT 22 | // ], 23 | [ 24 | 'name' => 'Voz', 25 | 'category' => Channel::TYPE_VOICE 26 | ], 27 | ]; 28 | 29 | public function handle(Team $team) 30 | { 31 | /** @var Guild $guild */ 32 | $guild = app('bot') 33 | ->discord() 34 | ->guilds 35 | ->get('id', config('bot.main_guild')); 36 | 37 | $this->createRole($team, $guild); 38 | 39 | $payload = $this->buildBaseCategory($guild, $team); 40 | $category = await($guild->channels->save($payload)); 41 | $this->createChannels($team, $guild, $category); 42 | } 43 | 44 | 45 | private function createChannels(Team $team, Guild $guild, Channel $channel): void 46 | { 47 | $team = $team->refresh(); 48 | $categoryId = $channel->id; 49 | $mentorId = $guild->roles->find(fn(Role $role) => $role->name === 'Pessoa Mentora')->id; 50 | $roleId = $team->role_id; 51 | $everyoneRole = config('bot.everyoneRole'); 52 | 53 | $channelsToUpdate = [$channel->id]; 54 | foreach ($this->newChannels as $newChannel) { 55 | 56 | $channel = $guild->channels->create([ 57 | 'name' => $newChannel['name'], 58 | 'parent_id' => $categoryId, 59 | 'type' => $newChannel['category'], 60 | 'permission_overwrites' => [ 61 | [ 62 | "id" => $everyoneRole, 63 | "type" => 0, 64 | "allow" => '0', 65 | "deny" => '1049600' 66 | ], 67 | [ 68 | "id" => $mentorId, 69 | "type" => 0, 70 | "allow" => '1049600', 71 | "deny" => '0' 72 | ], 73 | [ 74 | "id" => $roleId, 75 | "type" => 0, 76 | "allow" => '1049600', 77 | "deny" => '0' 78 | ] 79 | ], 80 | ]); 81 | 82 | /** @var Channel $fuckingChannel */ 83 | $fuckingChannel = await($guild->channels->save($channel)); 84 | $channelsToUpdate[] = $fuckingChannel->id; 85 | } 86 | 87 | $team->update(['channels_ids' => $channelsToUpdate]); 88 | } 89 | 90 | private function defaultPermissions(Guild $guild): array 91 | { 92 | return [ 93 | "id" => $guild->roles->first()->id, 94 | "type" => 0, 95 | "allow" => "0", 96 | "deny" => "1049600" 97 | ]; 98 | } 99 | 100 | private function buildBaseCategory(Guild $guild, Team $team): Part 101 | { 102 | return $guild->channels->create([ 103 | 'name' => 'Time ' . $team->getKey(), 104 | 'type' => Channel::TYPE_CATEGORY, 105 | 'permission_overwrites' => [ 106 | $this->defaultPermissions($guild) 107 | ], 108 | ]); 109 | } 110 | 111 | private function createRole(Team $team, Guild $guild): Role 112 | { 113 | $roleDTO = $guild->roles->create([ 114 | 'name' => 'Time ' . $team->getKey(), 115 | 'color' => 0x00FF00, 116 | 'hoist' => false, 117 | 'mentionable' => true, 118 | ]); 119 | 120 | $role = await($guild->roles->save($roleDTO)); 121 | $team->updateRole($role->id); 122 | 123 | return $role; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/Bot.php: -------------------------------------------------------------------------------- 1 | group(function () { 19 | // Route::get('/', fn () => 'Hello world!'); 20 | }); 21 | 22 | Route::middleware('api')->group(function () { 23 | // Route::get('/commands', fn () => collect($this->registeredCommands)->map(fn ($command) => [ 24 | // 'signature' => $command->getSignature(), 25 | // 'description' => $command->getDescription(), 26 | // ])); 27 | }); 28 | } 29 | 30 | public function afterBoot(): void 31 | { 32 | $activity = $this->discord()->factory(Activity::class, [ 33 | 'type' => Activity::TYPE_PLAYING, 34 | 'name' => 'VS Code.', 35 | ]); 36 | 37 | $this->discord()->updatePresence($activity); 38 | 39 | // $allowedGuilds = array_keys(config('bot.guilds')); 40 | // $this 41 | // ->discord 42 | // ->guilds 43 | // ->filter(fn ($guild) => !in_array($guild->id, $allowedGuilds)) 44 | // ->map(fn (Guild $guild) => $guild->leave()); 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Commands/SpawnRoomsCommand.php: -------------------------------------------------------------------------------- 1 | discord(); 57 | 58 | /** @var Guild $guild */ 59 | $guild = $discord->guilds->get('id', config('bot.main_guild')); 60 | 61 | $teams = Team::whereNotNull('guild_id')->get(); 62 | 63 | foreach ($guild->members as $member) { 64 | /** @var Member $member */ 65 | dump("dm sent to: " . $member->username); 66 | 67 | $member->sendMessage( 68 | Message::make(null) 69 | ->title("Aviso de migração de servidores") 70 | ->info() 71 | ->content(" 72 | Olá participante da Maratona Tech pelo RS. 73 | 74 | Realizamos uma manutenção no nosso servidor de Discord e migramos todos os canais de equipes para nosso servidor principal, com o objetivo de facilitar e centralizar a comunicação com todas as equipes, bem como o atendimento de mentoria. 75 | 76 | Por favor, pedimos que caso você tenha alguma dificuldade ou faltar alguém da sua equipe, abra um ticket no canal #suporte e iremos te ajudar. 77 | 78 | Agradecemos a sua paciência e dedicação até aqui! 79 | 80 | -------------------------------------------------------------------- 81 | Nome do servidor principal: Maratona Tech pelo RS 82 | Link para abrir um chamado de suporte: https://discord.gg/EtXtHg74 83 | -------------------------------------------------------------------- 84 | ") 85 | ->build() 86 | ); 87 | } 88 | 89 | $this 90 | ->message() 91 | ->title('SpawnRoomsCommand') 92 | ->content('Updated') 93 | ->send($message); 94 | } 95 | 96 | public function handle($message, $args) 97 | { 98 | $discord = app('bot')->discord(); 99 | 100 | /** @var \Discord\Parts\Guild\Guild $guild */ 101 | $guild = $discord->guilds->get('id', '1241451165112205322'); 102 | 103 | $listChannels = $guild->channels->filter(function ($channel) { 104 | return $channel->type === Channel::TYPE_TEXT; 105 | }); 106 | 107 | // delete voice rooms 108 | /** @var Channel $voiceRoom */ 109 | 110 | 111 | return; 112 | $teams = Team::whereNotNull('guild_id')->get(); 113 | 114 | foreach ($teams as $team) { 115 | 116 | $hasTeamRole = $guild->roles->find(fn($role) => $role->id === $team->role_id); 117 | 118 | if ($hasTeamRole) { 119 | dump('Role already exists: ' . $team->id); 120 | continue; 121 | } 122 | 123 | dump('Role Missing: ' . $team->id); 124 | app(SpawnTeamAction::class)->handle($team); 125 | 126 | $teamMembers = $team->members; 127 | foreach ($teamMembers as $teamMember) { 128 | $member = $guild->members->get('id', $teamMember->discord_id); 129 | $this->addParticipantRoles($member); 130 | } 131 | dump('Team added: ' . $team->id); 132 | 133 | 134 | } 135 | 136 | $this 137 | ->message() 138 | ->title('SpawnRoomsCommand') 139 | ->content('Hello world!') 140 | ->send($message); 141 | } 142 | 143 | 144 | private function addParticipantRoles(Member $member): void 145 | { 146 | $teamMember = \App\Models\Team\Member::query() 147 | ->where('discord_id', $member->id) 148 | ->first(); 149 | 150 | $hasRole = $member->roles->find(fn($role) => $role->id === $teamMember->team->role_id); 151 | 152 | if ($hasRole) { 153 | return; 154 | } 155 | 156 | try { 157 | await($member->addRole($teamMember->team->role_id)); 158 | } catch (\Exception $e) { 159 | dump($e->getMessage()); 160 | } 161 | 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/Console/Commands/LoadHackathonCommand.php: -------------------------------------------------------------------------------- 1 | option('participants')); 23 | $mentorsSpreadsheetUrl = str($this->option('mentors')); 24 | 25 | if (!$participantsSpreadsheetUrl->isUrl()) { 26 | $this->error("Invalid participants spreadsheet URL"); 27 | return self::FAILURE; 28 | } 29 | 30 | if (!$mentorsSpreadsheetUrl->isUrl()) { 31 | $this->error("Invalid mentors spreadsheet URL"); 32 | return self::FAILURE; 33 | } 34 | 35 | $this->wipeTeams(); 36 | $this->loadGuilds(); 37 | $this->loadParticipants($participantsSpreadsheetUrl); 38 | $this->loadMentors($mentorsSpreadsheetUrl); 39 | 40 | $this->info("vai caraio"); 41 | return self::SUCCESS; 42 | } 43 | 44 | private function loadParticipants(Stringable $participantsSpreadsheetUrl): void 45 | { 46 | 47 | $participantsList = str(file_get_contents($participantsSpreadsheetUrl)) 48 | ->explode(PHP_EOL) 49 | ->map(fn(string $participantEmail) => str($participantEmail)->lower()); 50 | 51 | $this->info(sprintf("Loading %s participants from $participantsSpreadsheetUrl", count($participantsList))); 52 | 53 | $participantsList 54 | ->each(function (Stringable $participantEmail) { 55 | Team::query()->updateOrCreate([ 56 | 'owner_email' => $participantEmail->toString() 57 | ], ['channels_ids' => []]); 58 | $this->info("Participant loaded"); 59 | }); 60 | } 61 | 62 | private function loadMentors(mixed $mentorsSpreadsheetUrl): void 63 | { 64 | $mentorsList = str(file_get_contents($mentorsSpreadsheetUrl)) 65 | ->explode(PHP_EOL) 66 | ->map(fn(string $mentorEmail) => str($mentorEmail)->lower()); 67 | 68 | $this->info(sprintf("Loading %s mentors from $mentorsSpreadsheetUrl", count($mentorsList))); 69 | 70 | $mentorsList 71 | ->each(function (Stringable $mentorEmail) { 72 | $this->info("Mentor loaded"); 73 | Mentor::query()->updateOrCreate([ 74 | 'email' => $mentorEmail->toString() 75 | ]); 76 | }); 77 | } 78 | 79 | private function wipeTeams(): void 80 | { 81 | DB::statement('SET FOREIGN_KEY_CHECKS=0'); 82 | 83 | Team::truncate(); 84 | Member::truncate(); 85 | 86 | DB::statement('SET FOREIGN_KEY_CHECKS=1'); 87 | } 88 | 89 | private function loadGuilds(): void 90 | { 91 | Guild::truncate(); 92 | foreach (config('bot.guilds') as $guild) { 93 | Guild::query()->create($guild); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/Enums/TeamNicheEnum.php: -------------------------------------------------------------------------------- 1 | 'Pre Disaster', 16 | self::During => 'During Disaster', 17 | self::Post => 'Post Disaster', 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Enums/TeamRoleEnum.php: -------------------------------------------------------------------------------- 1 | 'Pessoa Desenvolvedora', 19 | self::Artist => 'Pessoa Artista', 20 | self::Business => 'Pessoa de Negócios', 21 | self::Engineer => 'Pessoa Engenheira', 22 | self::Marketing => 'Pessoa de Marketing', 23 | self::HR => 'Pessoa de Recursos Humanos', 24 | self::Logistics => 'Pessoa de Logística/Mobilidade', 25 | }; 26 | } 27 | 28 | public function getDiscordId(): string 29 | { 30 | return match ($this) { 31 | self::Developer => '1241469983188582441', 32 | self::Business => '1241470085160243351', 33 | self::Engineer => '1241470066361634956', 34 | self::Marketing, self::Artist => '1241470339699970088', 35 | self::HR => '1241470768391393320', 36 | self::Logistics => '1241470469945692301', 37 | }; 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Events/GuildJoinEvent.php: -------------------------------------------------------------------------------- 1 | where('provider_id', $member->guild_id)->doesntExist()) { 30 | return; 31 | } 32 | $this->addTeamlessRole($member); 33 | $this->addParticipantRoles($member); 34 | $this->addMentorRole($member); 35 | } 36 | 37 | private function addTeamlessRole(Member $member) 38 | { 39 | if ($member->guild_id === config('bot.main_guild')) { 40 | return; 41 | } 42 | 43 | $hasRole = $member->roles->find(fn($role) => $role->name === 'Sem Time'); 44 | 45 | if ($hasRole) { 46 | return; 47 | } 48 | 49 | await($member->addRole( 50 | $member->guild->roles->find(fn($role) => $role->name === 'Sem Time') 51 | )); 52 | } 53 | 54 | private function addParticipantRoles(Member $member): void 55 | { 56 | $hasGuild = Guild::query() 57 | ->where('main_server', false) 58 | ->where('provider_id', $member->guild_id) 59 | ->exists(); 60 | 61 | if (!$hasGuild) { 62 | return; 63 | } 64 | 65 | $teamMember = \App\Models\Team\Member::query() 66 | ->where('discord_id', $member->id) 67 | ->whereHas('team', fn(Builder $team) => $team->where('guild_id', $member->guild_id)) 68 | ->first(); 69 | 70 | if (!$teamMember) { 71 | await($member->sendMessage('Você não está em nenhum time. Volte pro servidor principal e rode o comando /entrar-time com o e-mail do seu lider!')); 72 | await($member->sendMessage('Após entrar no time, saia e entre do servidor de times para receber suas permissões!')); 73 | dump("Member {$member->id} not found in any team"); 74 | return; 75 | } 76 | 77 | await($member->addRole($teamMember->team->role_id)); 78 | 79 | } 80 | 81 | private function addMentorRole(Member $member): void 82 | { 83 | if ($member->guild_id === config('bot.main_guild')) { 84 | return; 85 | } 86 | 87 | $mentor = Mentor::query() 88 | ->where('provider_id', $member->id) 89 | ->first(); 90 | 91 | if (!$mentor) { 92 | return; 93 | } 94 | 95 | await($member->addRole( 96 | $member->guild->roles->find(fn($role) => $role->name === 'Pessoa Mentora') 97 | )); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/Exceptions/CommandException.php: -------------------------------------------------------------------------------- 1 | title('Houve um erro ao processar seu comando!') 15 | ->error() 16 | ->field('Mensagem', $this->getMessage(), true) 17 | ->timestamp() 18 | ->build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Models/Guild.php: -------------------------------------------------------------------------------- 1 | 'timestamp', 20 | ]; 21 | } 22 | 23 | public function acceptInvite(MentorDTO $dto): void 24 | { 25 | $this->update([ 26 | 'accepted_at' => now(), 27 | 'provider_id' => $dto->member->user->id, 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Models/Team/Member.php: -------------------------------------------------------------------------------- 1 | TeamRoleEnum::class, 21 | ]; 22 | 23 | public function team(): BelongsTo 24 | { 25 | return $this->belongsTo(Team::class); 26 | } 27 | 28 | public function alreadyJoinedATeam(string $discordId): ?Member 29 | { 30 | return $this 31 | ->where('discord_id', $discordId) 32 | ->first(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Models/Team/Team.php: -------------------------------------------------------------------------------- 1 | TeamNicheEnum::class, 32 | 'channels_ids' => 'array', 33 | ]; 34 | } 35 | 36 | public function guild(): BelongsTo 37 | { 38 | return $this->belongsTo(Guild::class, 'guild_id', 'provider_id'); 39 | } 40 | 41 | public function members(): HasMany 42 | { 43 | return $this->hasMany(Member::class, 'team_id'); 44 | } 45 | 46 | public function hasMaxMembers(): bool 47 | { 48 | return $this->members_count >= 5; 49 | } 50 | 51 | 52 | public function addMember(JoinTeamDTO $dto): void 53 | { 54 | $this->members()->create([ 55 | 'discord_id' => $dto->member->id, 56 | 'role_type' => $dto->selectedMemberRoleType, 57 | 'github_username' => $dto->member 58 | ]); 59 | } 60 | 61 | public function updateRole(string $roleId): void 62 | { 63 | $this->update([ 64 | 'role_id' => $roleId 65 | ]); 66 | } 67 | 68 | public function findByOwnerEmail(string $code): ?Team 69 | { 70 | return $this->query()->where('owner_email', $code)->first(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | protected $fillable = [ 18 | 'username', 19 | 'discord_id', 20 | 'is_admin', 21 | ]; 22 | 23 | /** 24 | * The attributes that should be cast. 25 | * 26 | * @var array 27 | */ 28 | protected $casts = [ 29 | 'is_admin' => 'boolean', 30 | ]; 31 | 32 | /** 33 | * The highlighted Discord ID. 34 | * 35 | * @return string 36 | */ 37 | public function getHighlightAttribute() 38 | { 39 | return "<@{$this->discord_id}>"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Providers/BotServiceProvider.php: -------------------------------------------------------------------------------- 1 | commands([ 20 | LoadHackathonCommand::class, 21 | ]); 22 | } 23 | 24 | /** 25 | * Register any application services. 26 | * 27 | * @return void 28 | */ 29 | public function register() 30 | { 31 | parent::register(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/SlashCommands/JoinTeamCommand.php: -------------------------------------------------------------------------------- 1 | member->user->username . ' - ' . $interaction->member->user->id . ' - ' . now()->toDateTimeString() . ' - ' . now()->diffForHumans()); 68 | try { 69 | $message = DB::transaction(function () use ($interaction, $joinTeam, $spawnTeam) { 70 | $dto = JoinTeamDTO::makeFromInteraction($interaction); 71 | $team = $joinTeam->handle($dto)->refresh(); 72 | 73 | if (!$this->hasChannelsAndRoles($team)) { 74 | dump("generating channels for team - {$team->id}..."); 75 | $spawnTeam->handle($team); 76 | } 77 | 78 | return $this 79 | ->message() 80 | ->title('Olá Participante!') 81 | ->content(" 82 | Você entrou no time {$team->id}! 83 | 84 | Acesse a sala <#{$team->channels_ids[1]}> para conversar com seus colegas. 85 | ") 86 | ->build(); 87 | }); 88 | 89 | } catch (CommandException $e) { 90 | $message = $e->buildErrorMessage(); 91 | } 92 | $interaction->respondWithMessage($message, true); 93 | } 94 | 95 | private function getInvite(Team $team): Invite 96 | { 97 | $team = $team->refresh(); 98 | $channelId = collect($team->channels_ids)->reverse()->first(); 99 | $channel = $this->discord 100 | ->guilds 101 | ->find(fn(Guild $guild) => $guild->id == $team->guild_id) 102 | ->channels 103 | ->get('name', $channelId); 104 | 105 | 106 | $inviteDTO = $channel->invites->create([ 107 | 'max_age' => 36000, 108 | 'max_uses' => 100, 109 | 'temporary' => false, 110 | ]); 111 | 112 | return await($channel->invites->save($inviteDTO)); 113 | } 114 | 115 | public function options(): array 116 | { 117 | return [ 118 | [ 119 | 'name' => 'chave', 120 | 'description' => 'E-mail do lider ou código do time', 121 | 'type' => Command::MESSAGE, 122 | 'required' => true 123 | ], 124 | [ 125 | 'name' => 'area', 126 | 'description' => 'Sua área de atuação', 127 | 'type' => Command::MESSAGE, 128 | 'required' => true, 129 | 'choices' => collect(TeamRoleEnum::cases())->map(fn(TeamRoleEnum $role) => [ 130 | 'name' => $role->getDescription(), 131 | 'value' => $role->value 132 | ])->toArray() 133 | ], 134 | [ 135 | 'name' => 'github', 136 | 'description' => 'Seu usuário do GitHub, caso tenha! Ex: danielhe4rt', 137 | 'type' => Command::MESSAGE, 138 | 'required' => false 139 | ] 140 | ]; 141 | } 142 | 143 | private function hasChannelsAndRoles(Team $team): bool 144 | { 145 | // TODO: gerar time 146 | $guild = $this->discord() 147 | ->guilds 148 | ->get('id', config('bot.main_guild')); 149 | 150 | $hasChannels = $guild 151 | ->channels 152 | ->filter(fn($role) => in_array($role->id, $team->channels_ids)) 153 | ->count(); 154 | 155 | 156 | $hasRole = $guild 157 | ->roles 158 | ->filter(fn($role) => $role->id == $team->role_id) 159 | ->count(); 160 | 161 | return $hasChannels && $hasRole; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/SlashCommands/MentorCommand.php: -------------------------------------------------------------------------------- 1 | data->options->first()->name); 65 | $dto = MentorDTO::makeFromInteraction($interaction); 66 | 67 | try { 68 | $response = $command->getAction()->handle($dto); 69 | } catch (CommandException $e) { 70 | $response = $e->buildErrorMessage(); 71 | } 72 | 73 | $interaction->respondWithMessage($response, true); 74 | } 75 | 76 | 77 | public function options(): array 78 | { 79 | 80 | return [ 81 | [ 82 | "type" => Option::SUB_COMMAND, 83 | "name" => "entrar", 84 | "description" => "Entrar como mentor.", 85 | "options" => [ 86 | [ 87 | "type" => Option::STRING, 88 | "name" => "email", 89 | "description" => "Seu endereço de email cadastrado como mentor.", 90 | "required" => true 91 | ] 92 | ] 93 | ], 94 | [ 95 | "type" => Option::SUB_COMMAND, 96 | "name" => "pedir-ajuda", 97 | "description" => "Peça ajuda para nossos mentores de plantão!", 98 | "options" => [ 99 | [ 100 | "type" => Option::STRING, 101 | "name" => "tipo-mentoria", 102 | "description" => "Informe qual tipo de pessoa você precisa de mentoria.", 103 | "required" => true, 104 | "choices" => collect(TeamRoleEnum::cases())->map(fn(TeamRoleEnum $role) => [ 105 | 'name' => $role->getDescription(), 106 | 'value' => $role->value 107 | ])->push(['name' => 'Geral', 'value' => 'outro']) 108 | ->toArray() 109 | ], 110 | [ 111 | "type" => Option::STRING, 112 | "name" => "contexto", 113 | "description" => "Descreva seu desafio atual.", 114 | "required" => true 115 | ], 116 | ] 117 | ] 118 | ]; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/SlashCommands/OverviewCommand.php: -------------------------------------------------------------------------------- 1 | whereHas('members') 64 | ->count(); 65 | 66 | $totalMembers = Member::query() 67 | ->count(); 68 | 69 | $mentorsIds = Mentor::query() 70 | ->whereNotNull('provider_id') 71 | ->pluck('provider_id') 72 | ->toArray(); 73 | 74 | 75 | $onlineMentors = $interaction 76 | ->guild 77 | ->members 78 | ->filter(function (\Discord\Parts\User\Member $member) use ($mentorsIds) { 79 | return in_array($member->user->id, $mentorsIds) 80 | && $member->status === 'online'; 81 | })->count(); 82 | 83 | $interaction->respondWithMessage( 84 | $this->message() 85 | ->title('Estatísticas Gerais') 86 | ->fields([ 87 | 'Times Ativos' => $teamsWithOneOrMoreMembers, 88 | 'Participantes Ativos' => $totalMembers, 89 | 'Mentores Online' => $onlineMentors 90 | ]) 91 | ->timestamp() 92 | ->build() 93 | , true 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/SlashCommands/PingCommand.php: -------------------------------------------------------------------------------- 1 | message() 61 | ->content('Pong!') 62 | ->timestamp() 63 | ->build(); 64 | $interaction->respondWithMessage($message, true); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | create(); 6 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "app", 5 | "bootstrap", 6 | "config", 7 | "vendor", 8 | "database" 9 | ], 10 | "files": [ 11 | "composer.json" 12 | ], 13 | "exclude-composer-files": false, 14 | "compression": "GZ", 15 | "compactors": [ 16 | "KevinGH\\Box\\Compactor\\Php", 17 | "KevinGH\\Box\\Compactor\\Json" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laracord/laracord", 3 | "type": "project", 4 | "description": "Create Discord bots harnessing the full power of Laravel.", 5 | "keywords": ["framework", "laravel", "discord"], 6 | "license": "MIT", 7 | "require": { 8 | "php": "^8.2", 9 | "illuminate/database": "^11.5", 10 | "laracord/framework": "^2.0" 11 | }, 12 | "require-dev": { 13 | "fakerphp/faker": "^1.23", 14 | "laravel/pint": "^1.15" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "App\\": "app/", 19 | "Database\\Factories\\": "database/factories/", 20 | "Database\\Seeders\\": "database/seeders/" 21 | } 22 | }, 23 | "scripts": { 24 | "post-root-package-install": [ 25 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 26 | ] 27 | }, 28 | "config": { 29 | "preferred-install": "dist", 30 | "sort-packages": true, 31 | "optimize-autoloader": true, 32 | "allow-plugins": { 33 | "php-http/discovery": true 34 | } 35 | }, 36 | "minimum-stability": "dev", 37 | "prefer-stable": true 38 | } 39 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Laracord'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Application Version 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value determines the "version" your application is currently running 24 | | in. You may want to follow the "Semantic Versioning" - Given a version 25 | | number MAJOR.MINOR.PATCH when an update happens: https://semver.org. 26 | | 27 | */ 28 | 29 | 'version' => app('git.version'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Application Environment 34 | |-------------------------------------------------------------------------- 35 | | 36 | | This value determines the "environment" your application is currently 37 | | running in. This may determine how you prefer to configure various 38 | | services the application utilizes. This can be overridden using 39 | | the global command line "--env" option when calling commands. 40 | | 41 | */ 42 | 43 | 'env' => env('APP_ENV', 'production'), 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Application Timezone 48 | |-------------------------------------------------------------------------- 49 | | 50 | | Here you may specify the default timezone for your application, which 51 | | will be used by the PHP date and date-time functions. We have gone 52 | | ahead and set this to a sensible default for you out of the box. 53 | | 54 | */ 55 | 56 | 'timezone' => env('APP_TIMEZONE', 'UTC'), 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Autoloaded Service Providers 61 | |-------------------------------------------------------------------------- 62 | | 63 | | The service providers listed here will be automatically loaded on the 64 | | request to your application. Feel free to add your own services to 65 | | this array to grant expanded functionality to your applications. 66 | | 67 | */ 68 | 69 | 'providers' => [ 70 | App\Providers\BotServiceProvider::class, 71 | ], 72 | 73 | ]; 74 | -------------------------------------------------------------------------------- /config/bot.php: -------------------------------------------------------------------------------- 1 | '1240314593373327422', 5 | 'main_guild' => '1240311895362375791', 6 | 'teamsPerGuild' => 100, 7 | 'roomsPerTeam' => 3, 8 | 'everyoneRole' => '1240311895362375791', 9 | 'guilds' => [ 10 | '1240311895362375791' => [ 11 | 'provider_id' => '1240311895362375791', 12 | 'main_server' => true, 13 | 'invite_url' => 'https://discord.gg/4cAHq4rwzU', 14 | ], 15 | '1241451165112205322' => [ 16 | 'provider_id' => '1241451165112205322', 17 | 'main_server' => false, 18 | 'invite_url' => 'https://discord.gg/5urz8aJKRt', 19 | ], 20 | '1242919315674628228' => [ 21 | 'provider_id' => '1242919315674628228', 22 | 'main_server' => false, 23 | 'invite_url' => 'https://discord.gg/kTA388TvAa', 24 | ], 25 | '1242919447702929408' => [ 26 | 'provider_id' => '1242919447702929408', 27 | 'main_server' => false, 28 | 'invite_url' => 'https://discord.gg/UxRE3ugeAJ', 29 | ] 30 | ] 31 | ]; 32 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'mysql'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Database Connections 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Below are all of the database connections defined for your application. 27 | | An example configuration is provided for each database system which 28 | | is supported by Laravel. You're free to add / remove connections. 29 | | 30 | */ 31 | 32 | 'connections' => [ 33 | 34 | 'sqlite' => [ 35 | 'driver' => 'sqlite', 36 | 'url' => env('DB_URL'), 37 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 38 | 'prefix' => '', 39 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 40 | ], 41 | 42 | 'mysql' => [ 43 | 'driver' => 'mysql', 44 | 'url' => env('DB_URL'), 45 | 'host' => env('DB_HOST', '127.0.0.1'), 46 | 'port' => env('DB_PORT', '3306'), 47 | 'database' => env('DB_DATABASE', 'laravel'), 48 | 'username' => env('DB_USERNAME', 'root'), 49 | 'password' => env('DB_PASSWORD', ''), 50 | 'unix_socket' => env('DB_SOCKET', ''), 51 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 52 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 53 | 'prefix' => '', 54 | 'prefix_indexes' => true, 55 | 'strict' => true, 56 | 'engine' => null, 57 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 58 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 59 | ]) : [], 60 | ], 61 | 62 | 'mariadb' => [ 63 | 'driver' => 'mariadb', 64 | 'url' => env('DB_URL'), 65 | 'host' => env('DB_HOST', '127.0.0.1'), 66 | 'port' => env('DB_PORT', '3306'), 67 | 'database' => env('DB_DATABASE', 'laravel'), 68 | 'username' => env('DB_USERNAME', 'root'), 69 | 'password' => env('DB_PASSWORD', ''), 70 | 'unix_socket' => env('DB_SOCKET', ''), 71 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 72 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 73 | 'prefix' => '', 74 | 'prefix_indexes' => true, 75 | 'strict' => true, 76 | 'engine' => null, 77 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 78 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 79 | ]) : [], 80 | ], 81 | 82 | 'pgsql' => [ 83 | 'driver' => 'pgsql', 84 | 'url' => env('DB_URL'), 85 | 'host' => env('DB_HOST', '127.0.0.1'), 86 | 'port' => env('DB_PORT', '5432'), 87 | 'database' => env('DB_DATABASE', 'laravel'), 88 | 'username' => env('DB_USERNAME', 'root'), 89 | 'password' => env('DB_PASSWORD', ''), 90 | 'charset' => env('DB_CHARSET', 'utf8'), 91 | 'prefix' => '', 92 | 'prefix_indexes' => true, 93 | 'search_path' => 'public', 94 | 'sslmode' => 'prefer', 95 | ], 96 | 97 | 'sqlsrv' => [ 98 | 'driver' => 'sqlsrv', 99 | 'url' => env('DB_URL'), 100 | 'host' => env('DB_HOST', 'localhost'), 101 | 'port' => env('DB_PORT', '1433'), 102 | 'database' => env('DB_DATABASE', 'laravel'), 103 | 'username' => env('DB_USERNAME', 'root'), 104 | 'password' => env('DB_PASSWORD', ''), 105 | 'charset' => env('DB_CHARSET', 'utf8'), 106 | 'prefix' => '', 107 | 'prefix_indexes' => true, 108 | // 'encrypt' => env('DB_ENCRYPT', 'yes'), 109 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), 110 | ], 111 | 112 | ], 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Migration Repository Table 117 | |-------------------------------------------------------------------------- 118 | | 119 | | This table keeps track of all the migrations that have already run for 120 | | your application. Using this information, we can determine which of 121 | | the migrations on disk haven't actually been run on the database. 122 | | 123 | */ 124 | 125 | 'migrations' => [ 126 | 'table' => 'migrations', 127 | 'update_date_on_publish' => true, 128 | ], 129 | 130 | /* 131 | |-------------------------------------------------------------------------- 132 | | Redis Databases 133 | |-------------------------------------------------------------------------- 134 | | 135 | | Redis is an open source, fast, and advanced key-value store that also 136 | | provides a richer body of commands than a typical key-value system 137 | | such as Memcached. You may define your connection settings here. 138 | | 139 | */ 140 | 141 | 'redis' => [ 142 | 143 | 'client' => env('REDIS_CLIENT', 'phpredis'), 144 | 145 | 'options' => [ 146 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 147 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), 148 | ], 149 | 150 | 'default' => [ 151 | 'url' => env('REDIS_URL'), 152 | 'host' => env('REDIS_HOST', '127.0.0.1'), 153 | 'username' => env('REDIS_USERNAME'), 154 | 'password' => env('REDIS_PASSWORD'), 155 | 'port' => env('REDIS_PORT', '6379'), 156 | 'database' => env('REDIS_DB', '0'), 157 | ], 158 | 159 | 'cache' => [ 160 | 'url' => env('REDIS_URL'), 161 | 'host' => env('REDIS_HOST', '127.0.0.1'), 162 | 'username' => env('REDIS_USERNAME'), 163 | 'password' => env('REDIS_PASSWORD'), 164 | 'port' => env('REDIS_PORT', '6379'), 165 | 'database' => env('REDIS_CACHE_DB', '1'), 166 | ], 167 | 168 | ], 169 | 170 | ]; 171 | -------------------------------------------------------------------------------- /config/discord.php: -------------------------------------------------------------------------------- 1 | env('DISCORD_BOT_DESCRIPTION', 'The Laracord Discord Bot.'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Discord Token 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify your Discord bot token. You can find it under the 26 | | "Bot" section of your Discord application. Make sure to keep this 27 | | token private and never share it with anyone for security. 28 | | 29 | */ 30 | 31 | 'token' => env('DISCORD_TOKEN', ''), 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Gateway Intents 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Here you may specify the gateway intents for your Discord bot. This 39 | | will tell Discord what events your bot should receive. Intents can be 40 | | enabled in the Discord developer application portal under: 41 | | 42 | | Settings > Bot > Privileged Gateway Intents 43 | | 44 | */ 45 | 46 | 'intents' => Intents::getDefaultIntents() | Intents::MESSAGE_CONTENT | Intents::GUILD_MEMBERS, 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Command Prefix 51 | |-------------------------------------------------------------------------- 52 | | 53 | | Here you may specify the command prefix for the Discord bot. This 54 | | prefix will be used to distinguish commands from regular chat 55 | | messages. To use mentioning the bot as a prefix, use "@mention". 56 | | To use multiple prefixes, you may pass an array instead. 57 | | 58 | */ 59 | 60 | 'prefix' => env('DISCORD_COMMAND_PREFIX', '!'), 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Additional DiscordPHP Options 65 | |-------------------------------------------------------------------------- 66 | | 67 | | Here you may specify any additional options for the DiscordPHP client. 68 | | These options will be passed directly to the DiscordPHP client. 69 | | 70 | | For more information, see the DiscordPHP documentation: 71 | | ↪ 72 | | 73 | */ 74 | 75 | 'options' => [ 76 | 'loadAllMembers' => true, 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | HTTP Server 82 | |-------------------------------------------------------------------------- 83 | | 84 | | The Laracord HTTP server allows you to receive and respond to HTTP 85 | | requests from the bot at the specified address/port. This can be useful 86 | | for creating a RESTful API for your bot. 87 | | 88 | | The HTTP server is automatically started when a `routes.php` file is 89 | | present and contains valid routes. You can override this behavior by 90 | | setting this option to `false`. 91 | | 92 | */ 93 | 94 | 'http' => env('HTTP_SERVER', ':8080'), 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Timestamp Format 99 | |-------------------------------------------------------------------------- 100 | | 101 | | Here you may specify the timestamp format for the Discord bot. This 102 | | format will be used when formatting console output. You can set this 103 | | to `false` to disable timestamps. 104 | | 105 | */ 106 | 107 | 'timestamp' => 'h:i:s A', 108 | 109 | /* 110 | |-------------------------------------------------------------------------- 111 | | Bot Admins 112 | |-------------------------------------------------------------------------- 113 | | 114 | | Here you may manually specify bot admins without using the User model. 115 | | These users will have access to all bot admin commands. User's must 116 | | be specified by their Discord user ID. 117 | | 118 | */ 119 | 120 | 'admins' => [ 121 | '247397456361291776', // Nadachi 122 | '204122995579551744' // danielhe4rt 123 | ], 124 | 125 | /* 126 | |-------------------------------------------------------------------------- 127 | | Additional Commands 128 | |-------------------------------------------------------------------------- 129 | | 130 | | Here you may specify any additional commands for the Discord bot. These 131 | | commands will be loaded in addition to the commands automatically loaded 132 | | in your project. By default, the Laracord-provided help command is 133 | | is registered here. 134 | | 135 | */ 136 | 137 | 'commands' => [ 138 | Laracord\Commands\HelpCommand::class, 139 | ], 140 | 141 | /* 142 | |-------------------------------------------------------------------------- 143 | | Additional Services 144 | |-------------------------------------------------------------------------- 145 | | 146 | | Here you may specify any additional services to run asynchronously 147 | | alongside the Discord bot. These services will be loaded in addition 148 | | to the services automatically loaded from your project. 149 | | 150 | */ 151 | 152 | 'services' => [ 153 | // 154 | ], 155 | 156 | /* 157 | |-------------------------------------------------------------------------- 158 | | Additional Events 159 | |-------------------------------------------------------------------------- 160 | | 161 | | Here you may specify any additional events to listen for in your 162 | | Discord bot. These events will be registered in addition to the 163 | | events automatically registered from your project. 164 | | 165 | */ 166 | 167 | 'events' => [ 168 | // 169 | ], 170 | 171 | ]; 172 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('username')->index(); 17 | $table->string('discord_id')->index(); 18 | $table->boolean('is_admin')->default(false); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('users'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/0002_01_01_000000_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->string('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_05_15_154342_create_teams_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('guild_id')->nullable(); 14 | $table->string('role_id')->nullable(); 15 | $table->string('owner_email')->unique(); 16 | $table->string('niche_type')->default(TeamNicheEnum::Unknown->value); 17 | $table->integer('members_count')->default(0); 18 | $table->string('channels_ids')->nullable(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | public function down(): void 24 | { 25 | Schema::dropIfExists('teams'); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /database/migrations/2024_05_15_154840_create_members_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->foreignId('team_id') 13 | ->constrained('teams') 14 | ->cascadeOnDelete(); 15 | $table->string('discord_id')->unique(); 16 | $table->string('role_type'); 17 | $table->string('github_username')->nullable(); 18 | $table->timestamps(); 19 | 20 | $table->index('discord_id'); 21 | }); 22 | } 23 | 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('members'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2024_05_21_234316_create_mentors_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->string('email'); 13 | $table->string('provider_id')->nullable(); 14 | $table->timestamp('accepted_at')->nullable(); 15 | $table->timestamps(); 16 | }); 17 | } 18 | 19 | public function down(): void 20 | { 21 | Schema::dropIfExists('mentors'); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /database/migrations/2024_05_22_193613_create_guilds_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->string('provider_id'); 13 | $table->boolean('main_server'); 14 | $table->string('invite_url'); 15 | $table->integer('teams_count')->default(0); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down(): void 21 | { 22 | Schema::dropIfExists('guilds'); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(GuildsSeeder::class); 16 | $this->call(TeamsSeeder::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /database/seeders/GuildsSeeder.php: -------------------------------------------------------------------------------- 1 | create($guild); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /database/seeders/TeamsSeeder.php: -------------------------------------------------------------------------------- 1 | setTeamsPerGuild(); 29 | 30 | $teams = range(1, 300); 31 | 32 | DB::statement('SET FOREIGN_KEY_CHECKS=0'); 33 | 34 | Team::truncate(); 35 | foreach ($teams as $team) { 36 | Team::create([ 37 | 'owner_email' => sprintf('d+%s@d.com', $team), 38 | 'niche_type' => TeamNicheEnum::Unknown, 39 | 'members_count' => 0, 40 | 'channels_ids' => [], 41 | ]); 42 | } 43 | DB::statement('SET FOREIGN_KEY_CHECKS=1'); 44 | } 45 | 46 | private function setTeamsPerGuild(): void 47 | { 48 | $this->teamsPerGuild = config('bot.teamsPerGuild'); 49 | } 50 | } 51 | --------------------------------------------------------------------------------