Repository navigation
feat(streaming): ferramentas de live para os streamers da comunidade - #576
Merged
Merged
Conversation
…ar evento Uma mensagem `revocation` era gravada em twitch_event_logs com o event_type da inscrição (ex.: stream.online) e disparava TwitchEventReceived. Qualquer listener futuro (a overlay, por exemplo) trataria a revogação como um evento real da live. Agora a revogação só espelha o status enviado pela Twitch em twitch_subscriptions, criando a linha se ela não existir localmente. Nada vai para o log de eventos e nenhum evento é disparado.
O TwitchHelixConnector não lançava exceção em respostas 4xx/5xx, então todos os `catch (RequestException)` em volta dele nunca rodavam: - "Sync from Twitch" com token inválido recebia `data` vazio e apagava todas as inscrições locais, com notificação de sucesso. - `twitch:subscribe` listava criações recusadas como "created". - o mapeamento 403/409 do RegisterTwitchSubscriptionsAction nunca acontecia. O conector passa a usar AlwaysThrowOnErrors. O delete no admin só apaga a linha local quando a Twitch confirma, ou quando a inscrição já não existe lá (404); nos outros erros, avisa e cancela.
…abled O challenge de verificação era respondido sem tocar em twitch_subscriptions, então toda inscrição criada pelo painel ficava em webhook_callback_verification_pending para sempre. Como o registro só pula inscrições `enabled`, rodar de novo tentava recriar todas e recebia 409. A Twitch habilita a inscrição quando o challenge é respondido. Agora o webhook espelha a inscrição como `enabled` nesse momento, inclusive as criadas fora do painel (`twitch:subscribe`). O registro passa a usar firstOrCreate: se a verificação chegar antes da resposta do CreateSubscription, o status `enabled` não é rebaixado para pending.
…om 500 Com TWITCH_OAUTH_ENABLED ligado (o padrão) e sem client id/secret, o botão "Continuar com Twitch" dava 500: o provider lia as credenciais com config()->string(), que lança InvalidArgumentException, e o OAuthController só trata RuntimeException. Valor vazio passava direto e mandava o usuário para a Twitch com client_id em branco. O provider passa a lançar RuntimeException quando a credencial falta ou está vazia, como já faz a integração do GitHub. O login volta para a home e registra o aviso no log. O .env.example ganha o bloco da Twitch: OAuth desligado por padrão, segredo do EventSub e callback, que precisa ser HTTPS público.
O state era só criptografado, sem nada que o ligasse a quem começou o login. Um callback com state e code válidos, gerados em outra sessão, era aceito: no intent `link`, a conta do provider (Discord, GitHub, Twitch) era vinculada ao usuário logado, que depois podia ser acessado por esse provider. O returnUrl do state também redirecionava para fora. O redirect agora gera um nonce, guarda na sessão por provider e o envia no state. O callback web consome o nonce (uso único) e recusa o state que não bate, voltando para a home sem vincular nada. O fluxo mobile não tem sessão e segue como antes.
Nova role `streamer`, concedida pelo admin no UserResource como o super admin já é. Ela libera a ability `use-streamer-tools`, que vai decidir quem pede escopos de broadcaster na Twitch e quem acessa as telas de live. Super admin passa pela ability via Gate::before, já que o admin não consegue alterar os próprios papéis. O RolesSeeder só roda em dev, então uma migration cria a role em produção. Sem ela, as telas de usuário do admin quebrariam no UserRole::from() ao encontrar a role no banco.
…oadcaster Até aqui o conjunto de escopos vinha só do painel: no /app todo mundo pedia `user:read:email`, e os escopos de alerta (follows, subs, bits) só existiam no fluxo do admin. Agora quem tem `use-streamer-tools` e conecta a Twitch fora do /admin pede o conjunto `streamer` (TWITCH_OAUTH_SCOPES_STREAMER). O login de visitante e os membros comuns continuam só com `user:read:email`. TwitchScopes concentra a regra e é usada pelo redirect e pelo card de conexões, então o card mostra exatamente o que a Twitch vai pedir.
…opos Quem já tinha a Twitch conectada (pelo login, por exemplo) e depois recebe a role streamer ficava preso: o card só mostrava "Disconnect" e não havia como pedir os escopos novos sem desconectar. A conexão OAuth passa a guardar os escopos concedidos em `metadata.granted_scopes`, a partir do `scope` que a Twitch devolve no token. O card compara com o conjunto que o usuário precisa e, quando falta algo, mostra "Reautorizar" e a lista do que falta. Conexões antigas, sem escopos guardados, contam como o conjunto padrão de login, então membros comuns não veem o aviso.
…er admin A lista de papéis ficava toda travada quando o admin editava o próprio usuário. Com a role streamer isso virou atrito: o admin que faz live precisava de outro admin, ou do banco, só para se marcar como streamer. Agora só a opção de super admin fica travada no próprio form. O servidor também ignora o que vier nela, então nem um request montado à mão tira o super admin de quem está logado, nem promove alguém a super admin pelo próprio perfil. A ADR 0002 passa a registrar a regra nova.
Novo cluster "Minha Live" no /app, visível só para quem tem a ability `use-streamer-tools`. Ele começa com duas telas: - Painel: o canal da Twitch conectado (ou o convite para conectar), aviso quando faltam escopos, números dos últimos 30 dias, botões para testar cada alerta e a atividade recente. - Overlays: uma cena por card (Coworking, A live vai começar, Alertas e Chat), com o link para o OBS mascarado, copiar, e uma ação para gerar novos links. Só a conexão da Twitch é dado real. Números, atividade e links vêm de StreamingPreviewData, e um aviso de prévia deixa isso claro na tela. A ideia é validar layout e fluxo antes de modelar overlay e alertas.
…live O card da Twitch no Painel mandava o streamer para o perfil para conectar ou reautorizar. Agora as três ações ficam no próprio card: - Conectar Twitch, quando não há conexão. - Reautorizar, quando faltam escopos de streamer. - Desconectar, com confirmação, quando há conexão. Conectar e reautorizar apontam para o mesmo fluxo OAuth do /app, que devolve o streamer ao Painel no fim. Desconectar só alcança a conexão do usuário logado, e cada ação some quando não faz sentido, então o Filament recusa a chamada mesmo vinda de um request montado à mão.
…m pede O disconnectById do ConnectionHub buscava a identidade só pelo id, sem olhar o dono. Qualquer usuário logado podia chamar o método pelo Livewire, inclusive a partir do perfil em /app, e desconectar a conta de outra pessoa (Discord, GitHub, Twitch) só com o id dela. A busca agora fica restrita às identidades do próprio usuário. O super admin também alcança as conexões de tenant, que são as que a tela do painel admin lista. Fora desse escopo, a resposta é a mesma de um id que não existe: o aviso "Connection not found". O dono continua desconectando a própria conta, e o super admin continua desconectando as conexões de tenant pelo painel admin.
A sub-navegação da Minha Live sai da lateral e vira abas acima do conteúdo, que ocupa a largura toda da página. O tema do /app passa a ler o módulo panel-app inteiro, então as classes das páginas do cluster também entram no build do Tailwind.
…m React Novo módulo de apresentação para as overlays que o streamer coloca no OBS. Diferente dos painéis Filament, ele vai renderizar páginas com Inertia e React, aproveitando o Vite que o projeto já tem. Entram o inertiajs/inertia-laravel no Composer e o @inertiajs/react, react, react-dom e @vitejs/plugin-react no npm, com versão fixa como o resto das dependências de front. O módulo também ganha a linha `mod:panel-overlays` na tabela de labels de triagem.
…exemplo
As cenas do toolkit local de live chegam ao site como páginas Inertia:
Coworking, A live vai começar e Sala de voz. Cada uma abre em
/overlay/{token}/{cena}, sem login, como fonte de navegador do OBS.
Do toolkit vieram só os componentes, hooks e o CSS da overlay. O único
ponto que falava com a rede era o useOverlayFeed; agora ele assina um
feed de exemplo que gera chat, alertas, música, nível e sala de voz.
Quando o tempo real chegar, a troca acontece nesse mesmo ponto.
- O Inertia roda só nessas rotas, com template próprio e sem SSR.
- O Vite ganha o plugin do React e a entrada do módulo, e o TypeScript
com os tipos do React entram para checar o código portado.
- O token por enquanto só precisa ter o formato certo, e o canal é fixo.
- O catálogo de cenas da Minha Live passa a listar as três que existem.
- O Prettier do lint-staged passa a formatar arquivos .tsx.
Novo módulo de domínio para o backend das ferramentas de streamer: quem é streamer, de quais plataformas transmite, sessões de live, eventos de alerta e configurações das overlays. Ele não tem rota, view nem Filament. Os módulos integration-* e os painéis dependem dele. O módulo entra no composer.json da raiz com `^1.0.0`, como os outros he4rt/*, e ganha a linha `mod:streaming` na tabela de labels de triagem.
O CONTEXT.md fixa o vocabulário do módulo e as fronteiras com identity, activity, integration-twitch e os painéis. Os termos incluem streamer, fonte, leitor do chat, sessão, evento de stream, identidade solta e token da overlay. A ADR-0001 registra o modelo de dados: - o streamer é a raiz, 1:1 com o usuário; - as plataformas são as ExternalIdentity que ele já conectou, com um toggle por fonte em `streamer_sources`; - sessões e eventos são fatos imutáveis; - as configurações das cenas ficam num jsonb tipado; - o chat vai para `activity.messages`, com o espectador como identidade sem conta, e `messages` ganha uma coluna `platform`; - o tempo real vai num canal privado do Reverb, autenticado pelo token da overlay. A ADR também lista o que muda em outros módulos e o que ficou para depois: retenção do lake, XP de live, doações e YouTube.
O plano leva a ADR-0001 até o código em 7 fases: pré-requisitos fora do módulo, núcleo do streaming, sessões e eventos, chat, ingestão da Twitch, tempo real no panel-overlays e a Minha Live com dados reais. Cada passo tem o contexto com arquivo e linha, o código antes e depois e os cenários BDD. O plano também registra problemas achados ao planejar: - o formulário de usuário do admin não dispara os eventos de role do spatie; - o mesmo canal pode ter inscrições da comunidade e do streamer; - o PersistMessage quebra num retry da fila.
Primeira fase do plano do streaming. Prepara identity, activity e integration-twitch para receber dados de live, sem comportamento novo para quem usa o site. - identity: `IdentityProvider::streamingPlatforms()` diz quais contas transmitem ao vivo. Hoje só a Twitch. - identity: a desconexão passa a ser a Action `DisconnectExternalIdentity`, que emite `ExternalIdentityDisconnected`. O ConnectionHub e a Minha Live usam a Action. - identity: quem conecta uma conta que já existe como identidade sem dono assume essa identidade, com o histórico junto. Um índice parcial único impede duas identidades sem dono para a mesma conta. - activity: `messages` ganha a coluna `platform`, com default `discord`. O PersistMessage grava a plataforma da mensagem, e os 8 leitores que contam mensagens como Discord passam a filtrar por ela. - integration-twitch: a listagem de inscrições EventSub filtra pelo broadcaster e lê todas as páginas.
Segunda fase do plano. O módulo ganha o registro do streamer, sem ingestão e sem tempo real ainda. - Enums do domínio no `streaming`: `StreamEventType` (era o `StreamAlertType` do panel-app, e `gift-sub` vira `gift_sub`), `OverlayScene`, `StreamerStatus`, `ChatReader`, `SubTier` e `VoiceLayout`. O panel-app passa a importar deles. - `StreamerSettings` tipado em jsonb, com um VO por cena e os alertas ligados por padrão. Valor inválido vira o padrão na leitura. - Tabelas `streamers` e `streamer_sources`, com FK restritiva e soft delete. O token da overlay fica criptografado, e a busca usa o hash. - `RegenerateOverlayToken`, `ResolveOverlayToken` e o nome do canal privado com a impressão do token. - O streamer nasce no primeiro acesso à Minha Live e segue a role. O listener lê o gate depois do commit, então o super-admin não perde o streamer e trocar outras roles não gera evento. - O spatie passa a emitir eventos de role, e o formulário de usuário do admin salva com `syncRoles` numa transação, porque o `sync` direto na relação não emite nada. - Cada conta de live conectada vira uma fonte. Reconectar restaura a fonte e mantém o toggle.
Terceira fase do plano. O módulo passa a gravar as lives e os eventos do canal, e decide quando um evento vira alerta na overlay. - Tabelas `stream_sessions` e `stream_events`. Os dois são fatos imutáveis: o evento não tem `updated_at`, e a identidade tem só uma sessão aberta por vez, garantida por índice parcial. - O `details` do evento é um VO por tipo (sub, gift sub, bits e raid), com o cast lendo o `type` da própria linha. Follow não tem detalhes. - `StartStreamSession`, `UpdateStreamSession` e `EndStreamSession`. Um online repetido não duplica a sessão, e um online novo fecha a sessão que perdeu o offline. - `RecordStreamEvent` grava cada evento uma vez por origem e só alerta quando a fonte e o tipo de alerta estão ligados. Streamer desativado e canal sem fonte não gravam nada. - Broadcasts `alert.triggered`, `session.started` e `session.ended` no canal privado do streamer. Os de sessão respeitam o toggle da fonte. - O alerta de teste da Minha Live dispara o broadcast marcado como teste, sem gravar evento.
Quarta fase do plano. O chat das lives entra em `activity.messages`, do mesmo jeito que o Discord, sem criar conta para quem só assiste. - `PersistMessage` passa a ser idempotente pelo `provider_message_id` e aceita metadata. Um retry da fila não quebra mais no índice único. - `RecordChatMessage` liga a mensagem à identidade do membro, quando ele tem a Twitch conectada. Se não tem, cria uma identidade solta, sem `User` e com XP 0. Várias mensagens do mesmo espectador usam a mesma identidade. - `ChatMessageMetadata` guarda nome, cor, badges e fragmentos de texto ou emote. O `streaming` só lê e grava a metadata por esse VO. - `DeleteChatMessage` marca a mensagem apagada pela moderação e mantém a linha no banco. - Broadcasts `chat.message` e `chat.message-deleted` no canal do streamer, só quando a fonte está ligada e tem leitor de chat.
… bot Quinta fase do plano. Os eventos da Twitch saem do lake e viram sessão, alerta e chat no `streaming`. - `ProjectTwitchEventToStreaming` lê cada `TwitchEventLog` e chama a Action certa do `streaming`. O `stream.online` busca título e categoria na Helix só quando o canal tem fonte ativa. Se a Helix falhar, a sessão abre sem título. - O gift de N subs grava um evento só. Os subs de presente que chegam depois são descartados. - `SyncStreamerTwitchSubscriptions` mantém as inscrições EventSub de cada fonte: 9 de alerta, mais 2 de chat quando a fonte tem leitor. As inscrições ficam marcadas com a fonte, então desligar o streamer remove só as dele. As do canal da comunidade continuam. - A sincronização roda quando a conexão, o streamer ou a fonte mudam, e só com o segredo do EventSub configurado. - `TwitchBotTokenService` renova o token da conta bot e guarda o refresh token novo que a Twitch devolve. - O streamer escolhe as features no OAuth (alertas, chat pela própria conta, chat pela conta bot) e a Twitch pede só os escopos delas. A reautorização mantém os escopos já concedidos. Na primeira conexão, o leitor do chat sai dos escopos concedidos. - `services.twitch.scopes.streamer` sai da config. Sem escolha, o streamer recebe os escopos de alerta, que eram o mesmo conjunto.
Sexta fase do plano. A overlay do OBS deixa o canal fixo de exemplo e
passa a abrir com os dados do streamer dono do token.
- Reverb como driver de broadcast. A config foi publicada sozinha, e o
`.env.example` traz as variáveis do ambiente local. `laravel-echo` e
`pusher-js` entram com versão fixa para o front da overlay.
- A página da overlay resolve o token pelo `streaming` e responde 404
para token desconhecido ou streamer desativado. As props trazem o
canal privado, as configurações da cena, as 30 últimas mensagens do
chat e a live aberta.
- O estado inicial usa o mesmo formato dos broadcasts. Recarregar a
fonte no OBS mantém o chat na tela e não repete alertas antigos.
- `POST /overlay/{token}/broadcasting/auth` assina o canal privado sem
sessão de usuário. Só libera o canal do próprio streamer, recusa o
token antigo depois de regenerado e tem limite de 30 pedidos por
minuto.
- O enum de cena do `panel-overlays` sai. O controller usa o
`OverlayScene` do `streaming`.
O adaptador do `useOverlayFeed` (5.4) fica com o front da overlay. O
plano traz a tabela dos eventos e das props iniciais.
Sétima fase do plano. A Minha Live deixa os dados de exemplo e passa a ler e gravar no `streaming`. - O Painel mostra follows, subs (com os gifts), bits e raids dos últimos 30 dias, e os 10 eventos mais novos com um resumo de cada. Sem eventos, a atividade mostra um estado vazio. - "Conectar Twitch" abre um modal com alertas e chat. A opção de chat pela he4rtdevs só aparece com a conta bot configurada. - A lista de fontes liga e desliga cada canal e troca quem lê o chat. Se os escopos já cobrem o leitor novo, a troca vale na hora. Se não, o streamer vai para a Twitch pedir só o que falta. - Overlays usa o token real do streamer. Gerar novos links invalida os antigos na hora. - "A live vai começar" e "Sala de voz" ganham configuração, e a overlay aberta no OBS recebe a mudança pelo `settings.updated`. Os alertas ganham um toggle por tipo. - `StreamingPreviewData` e o aviso de prévia saem.
Oitava e última fase do plano. Os documentos passam a descrever o que existe, não o que estava previsto. - README do `streaming`, com o caminho de um evento até a overlay e o passo a passo local: Reverb, streamer, fonte, twitch-cli e conta bot. Ele também lista os limites atuais, como o adaptador do `useOverlayFeed`, que ainda é do front. - CONTEXT do `streaming` com a estrutura real e o status implementado. - CONTEXT do `integration-twitch` com o ETL, as inscrições por fonte, a conta bot e as features do streamer. O aviso antigo sobre o raid sai, porque o controller já grava o canal de destino. - A ADR-0001 ganha uma nota com os fatos que mudaram depois da decisão. A tabela original fica como estava.
A overlay assina o canal privado do streamer pelo Echo e traduz os eventos do Laravel para os DTOs do feed.ts: alertas, chat, mensagem apagada e configurações da cena chegam sem recarregar o OBS. O chat recente e as configurações da cena entram como estado inicial, então a cena já abre com o título e o horário do painel. A barra do topo mostra o usuário do streamer, e não mais o nome do canal privado. Com ?demo na URL, a overlay segue usando o feed de exemplo.
A EventSub manda só o set_id e a versão de cada badge, sem imagem. A mensagem era gravada com a URL nula, e a overlay montava um <img> sem src para cada badge. O ETL agora busca a imagem no catálogo da Helix: os badges globais e os do canal, que trocam o global (o badge de sub é de cada canal). O catálogo fica em cache por 6 horas e só é consultado quando a mensagem tem badge e o canal tem fonte ativa. Se a Helix falhar, a mensagem entra com o badge sem imagem, e a overlay não mostra esse badge.
…fila Um evento da Twitch passava por dois saltos de fila antes da overlay: o ETL e o broadcast. Com o worker do Redis dormindo até 3 segundos quando a fila está vazia, o alerta podia chegar 6 segundos depois. Agora o ETL roda dentro do request do webhook, e os broadcasts saem na hora (ShouldBroadcastNow). Os eventos de uma live são poucos, então o request continua curto. Uma falha no ETL é reportada e a Twitch recebe 204 mesmo assim, porque o evento já está no lake. Um 500 faria a Twitch reenviar, o reenvio cai no dedupe, e falhas seguidas revogam a inscrição. Os broadcasts usam ShouldRescue: com o Reverb fora do ar, o webhook e o painel seguem funcionando.
…rlay Só a mensagem apagada chegava na overlay. Um ban, um timeout ou um /clear não chegavam, e o spam de quem foi banido ficava na tela. A fonte com leitor de chat agora assina channel.chat.clear_user_messages e channel.chat.clear. O ban marca as mensagens do chatter nas últimas 24 horas e tira as linhas dele da overlay. O /clear grava streamers.chat_cleared_at e esvazia o chat; ao recarregar, a overlay não traz de volta o que foi limpo. O chat.message agora leva o chatterId, para a overlay saber de quem é cada linha. As fontes que já existem ganham as inscrições novas com php artisan twitch:sync-streamer-subscriptions. Também entra o plano das próximas fases da Minha Live e do chat transparente.
Uma cena nova, só com o chat da live, para pôr por cima de qualquer cena do OBS. São seis estilos: Linhas (o padrão), Balões, Painel, Destaque, Terminal e Vidro. Pelo painel, o streamer escolhe o estilo, o tempo para cada linha sumir (0 = nunca), o tamanho da fonte, a largura da coluna, a direção e o alinhamento. A overlay aberta no OBS muda na hora pelo settings.updated. Três comportamentos ficam fixos, sem ajuste: a linha que não cabe na fonte sai inteira, nunca cortada no topo; o nome com cor escura demais é clareado até ficar legível; e quem não tem cor ganha uma cor fixa a partir do login. O Vidro não desfoca a cena, porque a fonte de navegador do OBS não vê o que está atrás dela. Ele usa uma faixa clara translúcida com sombra forte no texto.
A página Minha Live › Chat mostra as últimas 50 mensagens e deixa o streamer agir só na overlay, sem mexer na Twitch: - ocultar uma mensagem (metadata.hidden_at, separado do deleted_at da moderação); - silenciar um chatter (streamers.settings.muted_chatters) e tirar o silêncio; - limpar o chat da overlay, com o mesmo ClearChat do /clear. O StreamerChatMessages vira a consulta única do chat do streamer: a página, o recentChat da overlay e as Actions usam a mesma regra, e uma mensagem de outro canal responde 404. A lista atualiza a cada 3 s e pausa com o mouse em cima.
O Painel ganha uma seção com uma verificação por item: conta da Twitch, endereço do webhook, inscrições, último evento e overlays conectadas. Cada uma diz a causa e, quando dá, traz o conserto ao lado. - Reparar inscrições confere o status real pelo ListSubscriptions, apaga o que a Twitch não tem mais, adota as do nosso callback e só então sincroniza. A sincronização passa a recriar as inscrições com falha e as pendentes há mais de 10 minutos. - O token do streamer vence em poucas horas e nada o renovava. A checagem da conta tenta renovar no 401 antes de pedir reconexão. - A seção carrega depois da página. O selo vermelho no menu usa só as verificações que leem o banco, sem chamar a Twitch a cada página.
…rlays Cada card mostra a própria cena num iframe com ?demo, em escala. A prévia nunca vai ao ar, porque o modo demo não assina o canal. O Chat renderiza em 960 × 540 para a coluna ficar legível no card, e as cenas transparentes trocam o fundo entre escuro, claro e xadrez. Salvar uma configuração muda o v da URL da prévia, e o iframe carrega de novo com o estilo novo. O topo da página diz quantas overlays estão abertas agora, com a mesma contagem do Reverb da saúde da integração. O número atualiza a cada 5 s sem renderizar a página de novo.
…o detalhe Durante a live, o Painel mostra um card com as somas da sessão aberta: follows, subs, bits, raids, mensagens, ritmo do chat nos últimos 5 min, chatters e overlays abertas. O alerta de teste pede confirmação só quando há live, e cada item da atividade recente tem "Repetir alerta", que manda o evento gravado de novo para a overlay sem gravar outro. A página Lives lista as sessões da mais nova para a mais antiga, com as somas de cada uma. Uma sessão sem evento e sem mensagem mostra "—", porque o mais provável é a Twitch não ter entregado os eventos. O detalhe traz a linha do tempo, quem mais falou e a comparação com a última live com dados, para que uma live que caiu não infle as setas.
…guração O Painel empilhava seis blocos com o mesmo peso. A saúde ocupava cinco linhas verdes quando estava tudo certo, a conta aparecia de novo em Fontes, e os números de 30 dias ficavam zerados fora da live. Agora a coluna larga mostra a live: o card "Ao vivo" durante a transmissão, ou a última live com as setas em relação à anterior com dados. A atividade vem logo abaixo. A coluna estreita junta a conta, a integração e as outras fontes. Um item ok da integração mostra só o título, e um item com problema mostra o detalhe e o botão de reparo. No celular, a conta e a integração vêm antes da atividade. O alerta de teste foi para a página de Overlays, num menu ao lado do contador de overlays abertas. O tempo relativo agora sai em pt-BR.
Traz da 4.x os avatares pelas contas vinculadas e os endpoints mobile da timeline. - ExternalIdentity e IdentityProvider ficam com os dois lados: o avatarUrl() da 4.x e o missingScopes() dos escopos da Twitch. - composer.lock parte do lock da 4.x e só acrescenta os pacotes desta branch (inertia, reverb, streaming e panel-overlays). - Shards regerados com a suíte completa depois do merge.
gvieira18
approved these changes
Oct 5, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Contexto
Eu faço live com um toolkit que roda na minha máquina: alerta de follow, chat na tela, cena de "a live vai começar"... Funciona, mas só serve pra mim. Se alguém da comunidade quer fazer live com a cara da He4rt, tem que montar tudo do zero ou depender de ferramenta de terceiro.
Esse PR traz isso pro site. Quem tem a role
streamerconecta a Twitch na Minha Live (/app), cola o link da cena no OBS e pronto: alerta e chat chegam na overlay em tempo real, sem rodar nada local.Se liga no caminho de um evento:
O que a pessoa streamer ganha:
/cleare mensagem apagada pela moderação da Twitch limpam a overlay também.Alterações
streaming (módulo novo, domínio)
streamer_sources): cada conta de live conectada vira uma fonte, com toggle e leitor do chat (a própria conta ou a conta bot).detailstipado por evento (sub, gift sub, bits, raid) e dedupe por origem.activity.messages. Quem só assiste vira uma identidade sem conta, com XP 0.StreamerSettingsem jsonb tipado, um VO por cena. Valor inválido vira o padrão na leitura.HealthChecke a contagem de overlays abertas pelosubscription_countdo Reverb.StreamSessionTotals(somas por subselect),StreamSessionHistory(live aberta, última encerrada, vizinhas e base da comparação) eStreamSessionChat(ritmo e quem mais falou).panel-overlays (módulo novo)
POST /overlay/{token}/broadcasting/authassina só o canal do próprio streamer, recusa token regenerado e tem limite de 30 por minuto.?demo, a cena usa o feed de exemplo e não assina o canal.integration-twitch
ProjectTwitchEventToStreamingdo lake prostreaming, rodando no request do webhook comShouldBroadcastNow. Antes, o alerta podia levar uns 6 segundos pra chegar, por causa dos dois saltos de fila.identity / activity / panel-admin
streamere a abilityuse-streamer-tools. O admin consegue se dar papéis comuns sem mexer no próprio super admin.messages.platform, e oPersistMessageficou idempotente peloprovider_message_id.Correções que vieram no caminho (vale olhar com carinho)
enabled(antes ficava pendente pra sempre). A revogação atualiza a inscrição em vez de virar evento.Migrations
streaming:streamers,streamer_sources,stream_sessions,stream_events,chat_cleared_atemstreamerse o índice destream_session_id.identity: rolestreamere índice único parcial pra identidade sem dono.activity:platformemmessages(defaultdiscord).integration-twitch:streamer_source_idemtwitch_subscriptions.Pra subir
reverb:startjunto da aplicação, com as variáveisREVERB_*eVITE_REVERB_*(estão no.env.example).TWITCH_EVENTSUB_SECRETe callback HTTPS público. Sem o segredo, todo webhook é recusado.TWITCH_BOT_USER_IDeTWITCH_BOT_REFRESH_TOKEN)./app. Se o Reverb ficar no mesmo host, o proxy manda só o upgrade de WebSocket pra ele. A outra saída é um host próprio pro Reverb.streamerpelo admin pra quem vai usar.Docs
CONTEXT.mde README dostreaming(o caminho do evento e o passo a passo local com Reverb e twitch-cli), ADR-0001 do modelo de dados e os planos emstreaming/docs/plans/.Plano de Testes
make check(rector, pint e phpstan)vendor/bin/pest --parallel: suíte completa, 2198 testespanel-app(184),streaming(125),integration-twitch(130) epanel-overlays(24)Pra testar na mão, o README do
streamingtem o passo a passo:php artisan reverb:start).streamerpro seu usuário e abra Minha Live › Painel.twitch event trigger channel.follow ...) ou use o menu Testar alerta em Overlays.Evidências
Os prints vão num comentário logo abaixo: Painel offline e ao vivo, celular, Lives, detalhe da live e o menu "Testar alerta".
Issues Relacionadas
Sem issue: isso veio da minha própria live.