-
Notifications
You must be signed in to change notification settings - Fork 46
feat(bot-discord): sala empresarial private rooms #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gvieira18
wants to merge
6
commits into
4.x
Choose a base branch
from
story/448-sala-empresarial
base: 4.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f60a26e
feat(bot-discord): sala empresarial private rooms
gvieira18 bed8e4a
fix(bot-discord): allow partner before denying everyone
gvieira18 c3ccccc
chore(bot-discord): remove 3 Pontos partner room
gvieira18 f337064
chore(deps): bump concurrently to 10.0.4
gvieira18 10d0058
feat(bot-discord): hide empresarial room from non-members
gvieira18 4132d97
Merge branch '4.x' into story/448-sala-empresarial
gvieira18 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
app-modules/bot-discord/docs/adr/0001-sala-empresarial-role-gated-private-rooms.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| --- | ||
| type: adr | ||
| title: 'Sala Empresarial — role-gated private voice rooms' | ||
| module: bot-discord | ||
| status: accepted | ||
| date: 2026-07-23 | ||
| --- | ||
|
|
||
| # Sala Empresarial — role-gated private voice rooms | ||
|
|
||
| Partner companies (**Empresa Parceira**) need private voice rooms on the guild. We add a | ||
| separate `/sala-empresarial empresa:<slug>` slash command that **converts an existing | ||
| `/sala`-tracked room** into a private one by stamping Discord permission overwrites: `deny` | ||
| `@everyone` and `allow` the selected company's **Partner Role** for `VIEW_CHANNEL`, | ||
| `CONNECT`, `SPEAK`, `USE_VAD`, `SEND_MESSAGES` and `READ_MESSAGE_HISTORY` (the last two lock | ||
| the room's built-in text chat). `MENTION_EVERYONE` is left unchanged by the command — it | ||
| keeps whatever the category inherits (every `/sala` room already blocks it). Because | ||
| `VIEW_CHANNEL` is denied to `@everyone`, the room is **fully hidden**: non-members cannot see | ||
| it in the channel list nor who is connected. | ||
|
|
||
| ## Considered Options | ||
|
|
||
| - **Convert existing room vs. a flag on `/sala`.** We convert. `/sala` is shared by the | ||
| whole guild; branching its logic and gating a `empresarial:true` flag by role would | ||
| couple a general command to partner concerns. A dedicated command mirrors `/sala-limite` | ||
| and keeps `/sala` untouched. | ||
| - **Explicit `empresa` parameter vs. inferring the caller's role.** Explicit parameter. A | ||
| caller can belong to more than one Empresa Parceira, so intersecting their roles with the | ||
| registry is ambiguous. The parameter picks exactly one company; we then validate the | ||
| caller actually holds that company's Partner Role and reject (ephemeral) otherwise. | ||
| - **Config registry vs. hardcoding `brd`.** A flat `bot-discord.roles.partners` | ||
| (`slug => role_id`) registry. Adding a second partner company is one env + one config | ||
| line, no code change. The slash-command `choices` are built from this map. Each partner's | ||
| Partner Role id is seeded as an env-var default in `config/bot-discord.php` (the source of | ||
| truth), so no role ids live in this ADR. | ||
| - **Stateless vs. persisting the empresarial state.** Stateless. We only convert | ||
| `/sala`-tracked rooms, which `DynamicVoiceTask` auto-deletes when empty, so the overwrites | ||
| die with the channel. `VoiceChannelDTO` is left unchanged and Discord's overwrites are the | ||
| single source of truth; re-running the command is a harmless re-stamp. There is no revert | ||
| command — a room un-privatizes by emptying and being deleted. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - This is the first permission-overwrite code in `bot-discord`; prior room commands only | ||
| set the channel's parent/user-limit and relied on category inheritance. | ||
| - Gating is by Partner Role membership, **not** room ownership (unlike `/sala-limite`) — any | ||
| member of the selected company may privatize the room. | ||
| - Because `VIEW_CHANNEL` is denied to `@everyone`, empresarial rooms disappear from the | ||
| channel list for non-members and their occupant list is hidden. Discord force-disconnects | ||
| any non-member connected at conversion time (losing view access ejects them from a voice | ||
| channel) — accepted, and consistent with a fully private room. | ||
|
gvieira18 marked this conversation as resolved.
|
||
72 changes: 72 additions & 0 deletions
72
app-modules/bot-discord/src/Actions/VoiceChannel/ConfigureEmpresarialRoomAction.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace He4rt\BotDiscord\Actions\VoiceChannel; | ||
|
|
||
| use He4rt\BotDiscord\DTO\EmpresarialOverwritePlan; | ||
| use He4rt\BotDiscord\DTO\EmpresarialRoomDecision; | ||
| use He4rt\BotDiscord\DTO\VoiceChannelDTO; | ||
| use He4rt\BotDiscord\Enums\EmpresarialRejectionReason; | ||
|
|
||
| /** | ||
| * Decides whether the caller may turn the voice room they are in into a Sala Empresarial | ||
| * for the selected company, and — if so — what permission overwrites to stamp. | ||
| * | ||
| * Pure decision: no Discord I/O. The command edge applies the returned plan. | ||
| */ | ||
| final class ConfigureEmpresarialRoomAction | ||
| { | ||
| /** | ||
| * @param list<string> $callerRoleIds Discord role ids held by the caller. | ||
| * @param list<VoiceChannelDTO> $activeChannels The `/sala`-tracked rooms cache. | ||
| */ | ||
| public function execute( | ||
| string $companySlug, | ||
| array $callerRoleIds, | ||
| ?string $currentChannelId, | ||
| array $activeChannels, | ||
| ): EmpresarialRoomDecision { | ||
| if (!$this->isInsideTrackedRoom($currentChannelId, $activeChannels)) { | ||
| return EmpresarialRoomDecision::reject(EmpresarialRejectionReason::NotInTrackedRoom); | ||
| } | ||
|
|
||
| $partnerRoleId = $this->resolvePartnerRoleId($companySlug); | ||
|
|
||
| if ($partnerRoleId === null) { | ||
| return EmpresarialRoomDecision::reject(EmpresarialRejectionReason::UnknownCompany); | ||
| } | ||
|
|
||
| if (!in_array($partnerRoleId, $callerRoleIds, strict: true)) { | ||
| return EmpresarialRoomDecision::reject(EmpresarialRejectionReason::MissingPartnerRole); | ||
| } | ||
|
|
||
| return EmpresarialRoomDecision::approve( | ||
| EmpresarialOverwritePlan::for($companySlug, $partnerRoleId), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @param list<VoiceChannelDTO> $activeChannels | ||
| */ | ||
| private function isInsideTrackedRoom(?string $currentChannelId, array $activeChannels): bool | ||
| { | ||
| if ($currentChannelId === null) { | ||
| return false; | ||
| } | ||
|
|
||
| return array_any($activeChannels, fn (VoiceChannelDTO $channel): bool => $channel->channelId === $currentChannelId); | ||
| } | ||
|
|
||
| private function resolvePartnerRoleId(string $companySlug): ?string | ||
| { | ||
| /** @var array<string, string> $partners */ | ||
| $partners = config('bot-discord.roles.partners', []); | ||
|
|
||
| if (!array_key_exists($companySlug, $partners)) { | ||
| return null; | ||
| } | ||
|
|
||
| return $partners[$companySlug]; | ||
| } | ||
| } |
54 changes: 54 additions & 0 deletions
54
app-modules/bot-discord/src/DTO/EmpresarialOverwritePlan.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace He4rt\BotDiscord\DTO; | ||
|
|
||
| /** | ||
| * The permission overwrites to stamp on a channel when it becomes a Sala Empresarial: | ||
| * deny `@everyone` and allow the resolved Partner Role for the same voice-privacy set. | ||
| * | ||
| * `VIEW_CHANNEL` is included, so the room is fully private: `@everyone` cannot see it in | ||
| * the channel list nor who is connected, while the Partner Role can. Denying view also | ||
| * force-disconnects any non-partner member connected at conversion time. | ||
| * | ||
| * `MENTION_EVERYONE` relies on the category's existing inheritance, so it does not appear | ||
| * here. | ||
| */ | ||
| final readonly class EmpresarialOverwritePlan | ||
| { | ||
| /** | ||
| * Discord channel-permission keys that gate a private voice room. | ||
| * | ||
| * @var list<string> | ||
| */ | ||
| public const array PRIVATE_ROOM_PERMISSIONS = [ | ||
| 'view_channel', | ||
| 'connect', | ||
| 'speak', | ||
| 'use_vad', | ||
| 'send_messages', | ||
| 'read_message_history', | ||
| ]; | ||
|
|
||
| /** | ||
| * @param list<string> $denyEveryone Permissions denied to `@everyone`. | ||
| * @param list<string> $allowPartnerRole Permissions allowed for the Partner Role. | ||
| */ | ||
| private function __construct( | ||
| public string $companySlug, | ||
| public string $partnerRoleId, | ||
| public array $denyEveryone, | ||
| public array $allowPartnerRole, | ||
| ) {} | ||
|
|
||
| public static function for(string $companySlug, string $partnerRoleId): self | ||
| { | ||
| return new self( | ||
| companySlug: $companySlug, | ||
| partnerRoleId: $partnerRoleId, | ||
| denyEveryone: self::PRIVATE_ROOM_PERMISSIONS, | ||
| allowPartnerRole: self::PRIVATE_ROOM_PERMISSIONS, | ||
| ); | ||
| } | ||
| } |
34 changes: 34 additions & 0 deletions
34
app-modules/bot-discord/src/DTO/EmpresarialRoomDecision.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace He4rt\BotDiscord\DTO; | ||
|
|
||
| use He4rt\BotDiscord\Enums\EmpresarialRejectionReason; | ||
|
|
||
| /** | ||
| * Outcome of the Sala Empresarial decision seam: either a rejection reason or an | ||
| * approved overwrite plan. Exactly one of {@see $rejection}/{@see $plan} is set. | ||
| */ | ||
| final readonly class EmpresarialRoomDecision | ||
| { | ||
| private function __construct( | ||
| public ?EmpresarialRejectionReason $rejection, | ||
| public ?EmpresarialOverwritePlan $plan, | ||
| ) {} | ||
|
|
||
| public static function reject(EmpresarialRejectionReason $reason): self | ||
| { | ||
| return new self(rejection: $reason, plan: null); | ||
| } | ||
|
|
||
| public static function approve(EmpresarialOverwritePlan $plan): self | ||
| { | ||
| return new self(rejection: null, plan: $plan); | ||
| } | ||
|
|
||
| public function isApproved(): bool | ||
| { | ||
| return $this->plan instanceof EmpresarialOverwritePlan; | ||
| } | ||
| } |
27 changes: 27 additions & 0 deletions
27
app-modules/bot-discord/src/Enums/EmpresarialRejectionReason.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace He4rt\BotDiscord\Enums; | ||
|
|
||
| /** | ||
| * Why a `/sala-empresarial` privatization request was rejected by the decision seam. | ||
| */ | ||
| enum EmpresarialRejectionReason: string | ||
| { | ||
| case NotInTrackedRoom = 'not-in-tracked-room'; | ||
| case MissingPartnerRole = 'missing-partner-role'; | ||
| case UnknownCompany = 'unknown-company'; | ||
|
|
||
| /** | ||
| * Human-readable, ephemeral-friendly message shown to the caller. | ||
| */ | ||
| public function message(): string | ||
| { | ||
| return match ($this) { | ||
| self::NotInTrackedRoom => '❌ Você precisa estar dentro de uma sala criada com /sala para usar este comando!', | ||
| self::MissingPartnerRole => '❌ Você não faz parte da empresa selecionada e não pode torná-la privada.', | ||
| self::UnknownCompany => '❌ Empresa parceira não encontrada.', | ||
| }; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.