Toolbox

Bem-vinde ao meu toolbox!

Neste site você encontrará alguns minicódigos, fórmulas de Google Sheets, tutoriais, ou qualquer outra coisa que eu tenha feito e ache útil o bastante para compartilhar hehe :p

Além disso, você também encontrará alguns projetos pessoais que eu tenha desenvolvido, então sinta-se a vontade para navegar e favoritar esta página para acessar no futuro :3

Dúvidas? Comentários? Fale comigo por [email protected].


Sobre a autora

Oi, eu sou a Dani! ^^

Sou uma mulher trans, programadora (chocante)1, editora de videos, futura RPGista e aspirante a arteira :p

Na área de programação, tenho curtido estudar front-end web, HTML/CSS e Javascript. Apesar de não ser analista de dados, tenho estudado bastante Excel/Google Sheets, SQL e modelagem de dados por conta do trabalho. Ainda não achei contexto para aprender Node.JS, mas provavelmente vai ser o meu ponto de partida pra estudar back-end :)

Sou editora de videos para os canais Pra Quem Gosta, com foco atual em RPG, e Cosmic Wolf, principalmente com cortes de lives. Recomendo demais seguir ambos ^^

Também posto muuuuito esporadicamente algumas artes no meu Instagram, me segue por lá!

1

Se você não entendeu, um minicontexto :3

Tradutor de Língua Antiga
RPG Chrysopoeia

Língua Antiga é um idioma criado pelo Cian no seu cenário de Tormenta20, Chrysopoeia. Como fã da sua obra e da campanha de Chrysopoeia no canal "Pra Quem Gosta", eu quis criar um sistema de tradução e escrita da Língua Antiga for fun, por que ia ficar legal e, bem... este foi o resultado (:

Este projeto tem alguns aspectos que eu gostei muito de explorar, como estudo do HTML Canva para gerar imagens localmente, manipulação de strings para traduzir a gramática do idioma, entre outros. Entretanto, essas são coisas que irei documentar mais no futuro ^^

Créditos

Não foi utilizado nenhuma IA generativa para "criar" nenhum aspecto deste projeto.

🎨 Galeria de artes

Estou guardando aqui todas as artes que eu lembro de salvar. É legal documentar o processo de aprendizagem, ajuda a ver minha evolução :)

202520242023

Artes de 2025

Artezinha de Biel, OC da Nebu pro Desafio semanal do Artistas Cuiabá ˆˆ

Kasane Teto curtindo um som de boas :)

Artes de 2024

Íris Hermona, arqueóloga, historiadora, e minha personagem no RPG Chrysopoeia ^^

Neo Politan, de RWBY - pro Desafio de 30min do Artistas Cuiabá :3

Velvet Scarlatina, de RWBY - pro Desafio de 30min do Artistas Cuiabá :3

Rabisquinho inspirado numa música da JamieP, e a primeira vez que eu tentei fazer um desenho com limite de tempo, pro Desafio de 30min do grupo de Artistas Cuiabá :3

Rabisco de uma foto da Dabs porque eu achei a pose legal hehe

"X voltou, não dá pra essa praga morrer de vez não??" - Mais um rabisquinho rápidinho hehe

"Comecei minha TH, where is ma boobas :p" - Rabisco rápido pra praticar :)

Agni, minha tiefling, agora finalizada :)

Madeline, de Celeste - um rascunho que eu comecei num encontro do grupo de Artistas Cuiabá, mas que euu nunca acabei •- •

Animação de uma cena do RPG Jornada do Golem que me pegou muito na época hehe

Rabisco da Chaos, OC da CosmicWolf, num estilo mais Panty and Stocking :)

"Espero que 2024 seja um bom ano :)"

Artes de 2023

Rascunho de uma tiefling que eu fiz pra praticar :)

Fanart da SpiderGwen depois que eu vi o primeiro Aranhaverso hehe

Fanart da Oblivion Cosmic Radiation, variante de uma OC da CosmicWolf)

Fanart de OneShot, a Niko comendo uma panquequinha antes de sair da cidade

Ladra de Batatas me cooptando para o caos >:3

Kelly, minha fursona :3

Damian, personagem que eu criei pra jogar RPG Ordem Paranormal, mas que nunca viu a luz do dia ao menos não antes de se tornar Daniele :)

Dabes, do Pra Quem Gosta - uma artezinha que eu fiz de aniversário pra ela :)

Crises de disforia pré-transição ;-;

Rabisco rápido da Ladra de Batatas puta :p

"A Moderadora", uma arte bobinha que eu fiz de um tweet da Dabs (que eu perdi na morte do twitter ;-;)

Fanart da sona da minha amiga Larinha ^^

Fanart de Sirius, estrela de Supernova Squad

Minha primeira arte digital, o Rabscleido (personagem da CosmicWolf) atrapalhando as lives :3

Fórmulas customizadas do Google Sheets - Índice

Como utilizar

Se você nunca usou e não está familiarizado com fórmulas customizadas no Google Sheets, comece por aqui! Leia mais.

=CPFCHECK(cpf)

Valida o CPF, calculando e verificando os dois dígitos verificadores. Leia mais.

=EXT_NOME(nome_completo)

Obtém o nome da pessoa, extraindo todos os caracteres anteriores ao primeiro espaço do texto. Leia mais.

=EXT_SOBRENOME(nome_completo)

Obtém o sobrenome da pessoa, extraindo todos os caracteres posteriores ao primeiro espaço do texto. Leia mais.

=LINKPAGINA(nomeDaPagina)

Obtém um link que leva diretamente à página na planilha a partir do nome da página. Fórmula útil para documentos com muitas páginas, pois pode-se utilizá-la para criar uma página de índice automatizada. Leia mais.

=PROCTUDO(informacao_a_pesquisar; coluna_a_pesquisar; coluna_a_retornar)

Fórmula com função parecida do PROCV: ela procura um valor na planilha e retorna outro valor de outra coluna; mas caso o valor procurado apareça mais de uma vez, retorna todos os valores correlacionados, separados por ponto-e-vírgula. Leia mais.

Insere um QRCode do link informado na célula. Leia mais.

=REMOVER_ACENTOS(texto_a_remover_acentos)

Substitui os principais acentos da string para caracteres não-acentuados. Leia mais.

Como utilizar funções nomeadas

O Google Sheets possui duas formas para criar fórmulas customizadas nas suas planilhas: Funções nomeadas e Funções no Apps Script. Depois de instalar uma fórmula customizada, você pode chamar ela na sua planilha como qualquer fórmula normal, como =SUAFORMULA(A2).

Neste site você pode encontrar algumas fórmulas úteis no dia-a-dia, e abaixo você verá como instalar elas na sua planilha.

Índice:

  1. Funções nomeadas
    1.1. Como instalar
  2. Funções no Apps Script
    2.1 Como instalar
  3. Links de referência

Funções nomeadas

Imagem ilustrativa para função nomeada

Não é incomum algumas fórmulas no Google Sheets ficarem estupidamente grandes. Um exemplo neste próprio documento é a CPFCHECK(), fórmula matemática para calcular o dígito verificador do CPF, mas que é enorme e envolve referenciar a mesma célula dúzias de vezes.

Para estes casos, o Google Sheets permite criar funções nomeadas, que é um jeito de "apelidar" essa fórmula pra algo mais lembrável e reutilizável na sua planilha.

Para saber mais, leia este artigo do suporte do Google.

Como instalar

  1. Clique em Dados e em Funções nomeadas.

Imagem mostrando onde encontrar a opção acima descrita

  1. Selecione Adicionar nova função.

Imagem mostrando onde encontrar a opção acima descrita

  1. As funções nomeadas normalmente precisam de 3 campos: Nome da função, Marcadores de posição de argumentos e Definição da fórmula.

Imagem mostrando onde encontrar a opção acima descrita

Neste site, as fórmulas também estão organizadas pelos mesmos campos, portanto basta copiar e colar.

Imagem mostrando onde encontrar a opção acima descrita
Nota

Aperte "enter" para cada marcador de posição que você colar.

  1. Aperte Próxima e em Criar.

Imagem mostrando onde encontrar a opção acima descrita Imagem mostrando onde encontrar a opção acima descrita

Funções no Apps Script

Google Apps Script é um ambiente que permite desenvolver aplicações simples em Javascript que rodam nos servidores do Google e integram ferramentas da GSuite e outras externas que possuam APIs públicas.

No Google Sheets, o Apps Script também pode ser usado para criar fórmulas customizadas que possam se aproveitar da capacidade do Javascript de processar dados de forma mais complexa, ou de conexões com os serviços do Google.

Para saber mais, leia este artigo no suporte do Google.

Como instalar

  1. Clique em Extensões e em Apps Script.

Imagem mostrando onde encontrar a opção acima descrita

  1. Copie o código da função customizada, cole na última linha do documento e salve.

Imagem mostrando onde encontrar a opção acima descrita

=CPFCHECK(cpf)

Descrição: Valida o CPF, calculando e verificando os dois dígitos verificadores. O método de verificação é baseado nesse artigo: Só Matemática -Cálculo do dígito verificador do CPF. Utilize apenas números para verificar o CPF.

Te ajudei? Me manda um cafézinho ^^

[email protected]

Versão para uso como função nomeada

Nome da função:

CPFCHECK

Marcadores de posição de argumentos:

cpf

Definição da fórmula:

=BYROW(TEXT(cpf;"00000000000");
    LAMBDA(λ;
        IF(
            ISNUMBER(VALUE(λ));IF(
                LEN(λ)=11;IF(
                    OR(
                        λ="00000000000";
                        λ="11111111111";
                        λ="22222222222";
                        λ="33333333333";
                        λ="44444444444";
                        λ="55555555555";
                        λ="66666666666";
                        λ="77777777777";
                        λ="88888888888";
                        λ="99999999999"
                    )=FALSE;IF(
                        RIGHT(
                            MOD((SUM(
                                (MID(λ;1;1)*10);
                                (MID(λ;2;1)*9);
                                (MID(λ;3;1)*8);
                                (MID(λ;4;1)*7);
                                (MID(λ;5;1)*6);
                                (MID(λ;6;1)*5);
                                (MID(λ;7;1)*4);
                                (MID(λ;8;1)*3);
                                (MID(λ;9;1)*2);;
                            )*10);11);1)=(MID(λ;10;1));IF(
                            RIGHT(
                                MOD((SUM(
                                    (MID(λ;1;1)*11);
                                    (MID(λ;2;1)*10);
                                    (MID(λ;3;1)*9);
                                    (MID(λ;4;1)*8);
                                    (MID(λ;5;1)*7);
                                    (MID(λ;6;1)*6);
                                    (MID(λ;7;1)*5);
                                    (MID(λ;8;1)*4);
                                    (MID(λ;9;1)*3);
                                    (MID(λ;10;1)*2);
                                )*10);11);1)=(MID(λ;11;1));
                                TRUE;
                            FALSE);
                        FALSE);
                    FALSE);
                FALSE);
            FALSE
        )
    )
)

Versão para uso no Google Apps Script

/**
 *  Valida o CPF, verificando os dois dígitos verificadores.
 *  O CPF deve ser formatado como texto e não ter ponto ou traço.
 *  
 *  Função por: https://github.com/DaniFluffyLab
 *
 * @param {string} cpf
 * @returns {boolean}
 * @customfunction
 */

function cpfcheck(cpf) {

    // Se for dado único, executar função
    if (!Array.isArray(cpf)) return cpfcheck_(cpf)

    // Se array, separar dados e executar função 
    let arrayValue = cpf
    let arrayReturn = []
    arrayValue.forEach((row) => {
        let rowReturn = []
        row.forEach((cell) => {
            rowReturn.push(

                // Fórmula a calcular
                cpfcheck_(cell)

            )
        })
        arrayReturn.push(rowReturn)
    })
    return arrayReturn


    // Função a executar
    function cpfcheck_(cpf) { // Verifica se CPF existe
      
        // Verificar se possui outros caracteres
        if (cpf.length != 11) return false;
      
        // Elimina CPFs invalidos conhecidos	
        if (cpf.length != 11 ||
          cpf == "00000000000" ||
          cpf == "11111111111" ||
          cpf == "22222222222" ||
          cpf == "33333333333" ||
          cpf == "44444444444" ||
          cpf == "55555555555" ||
          cpf == "66666666666" ||
          cpf == "77777777777" ||
          cpf == "88888888888" ||
          cpf == "99999999999") return false;
      
        // Valida 1o digito	
        add = 0;
        for (i = 0; i < 9; i++)
          add += parseInt(cpf.charAt(i)) * (10 - i);
        rev = 11 - (add % 11);
        if (rev == 10 || rev == 11)
          rev = 0;
        if (rev != parseInt(cpf.charAt(9))) return false;
      
        // Valida 2o digito	
        add = 0;
        for (i = 0; i < 10; i++)
          add += parseInt(cpf.charAt(i)) * (11 - i);
        rev = 11 - (add % 11);
        if (rev == 10 || rev == 11)
          rev = 0;
        if (rev != parseInt(cpf.charAt(10))) return false;
      
        return true;
      }

}

Te ajudei? Me manda um cafézinho ^^

[email protected]

=EXT_NOME(nome_completo)

Descrição: Obtém o nome da pessoa, extraindo todos os caracteres anteriores ao primeiro espaço do texto.1

Te ajudei? Me manda um cafézinho ^^

[email protected]

Versão para uso como função nomeada

Nome da função:

EXT_NOME

Marcadores de posição de argumentos:

nome_completo

Definição da fórmula:

=EXT.TEXTO(nome_completo;1;LOCALIZAR(" ";nome_completo;1))
1

Encontrei essa fórmula no site do Prof. Alexandre Alcantara e adaptei para funcionar como função customizada no Google Sheets.

=EXT_SOBRENOME(nome_completo)

Descrição: Obtém o sobrenome da pessoa, extraindo todos os caracteres posteriores ao primeiro espaço do texto.1

Te ajudei? Me manda um cafézinho ^^

[email protected]

Versão para uso como função nomeada

Nome da função:

EXT_SOBRENOME

Marcadores de posição de argumentos::

nome_completo

Definição da fórmula:

=EXT.TEXTO(nome_completo;LOCALIZAR(" ";nome_completo;1)+1;NÚM.CARACT(nome_completo))
1

Encontrei essa fórmula no site do Prof. Alexandre Alcantara e adaptei para funcionar como função customizada no Google Sheets.

=LINKPAGINA(nomeDaPagina)

Descrição: Obtém um link que leva diretamente à página na planilha a partir do nome da página. Fórmula útil para documentos com muitas páginas, pois pode-se utilizá-la para criar uma página de índice automatizada.

Te ajudei? Me manda um cafézinho ^^

[email protected]

Versão para uso no Google Apps Script

/**
 *  Obtém um link que leva diretamente à página na planilha.
 *
 * @param {string} nomeDaPagina
 * @returns {boolean}
 * @customfunction
 */

function LinkPagina(nomeDaPagina) {

    // Se for dado único, executar função
    if (!Array.isArray(nomeDaPagina)) return LinkPagina_(nomeDaPagina)

    // Se array, separar dados e executar função 
    let arrayValue = nomeDaPagina
    let arrayReturn = []
    arrayValue.forEach((row) => {
        let rowReturn = []
        row.forEach((cell) => {
            rowReturn.push(

                // Fórmula a calcular
                LinkPagina_(cell)

            )
        })
        arrayReturn.push(rowReturn)
    })
    return arrayReturn


    // Função a executar
    function LinkPagina_(nome) {
        try {
            return `https://docs.google.com/spreadsheets/d/${SpreadsheetApp.getActiveSpreadsheet().getId()}/edit#gid=${SpreadsheetApp.getActiveSpreadsheet().getSheetByName(nome).getSheetId()}`
        } catch (e) {
            return "Página não encontrada"
        }
    }

}

=PROCTUDO(informacao_a_pesquisar; coluna_a_pesquisar; coluna_a_retornar)

Descrição: Fórmula com função parecida do PROCV: ela procura um valor na planilha e retorna outro valor de outra coluna; mas caso o valor procurado apareça mais de uma vez, retorna todos os valores correlacionados, separados por ponto-e-vírgula.

Por exemplo, veja a tabela a seguir:

Planilha de exemplo

Neste cenário, caso se deseje obter todas as funções de um CPF, utilizar o PROCV não resolveria, já que só me retornaria a primeira referência do CPF.

Para isso, utilize o PROCTUDO da seguinte maneira:

              Coluna onde o CPF está
                        ⬇
=PROCTUDO(12312312387; B:B; D:D)
               ⬆             ⬆
             CPF a      Coluna onde
            procurar   a função está

Lembre-se que as duas colunas referenciadas precisam ser do mesmo tamanho.

Te ajudei? Me manda um cafézinho ^^

[email protected]

Versão para uso como função nomeada

Nome da função:

PROCTUDO

Marcadores de posição de argumentos::

informacao_a_pesquisar
coluna_a_pesquisar
coluna_a_retornar

Definição da fórmula:

=BYROW(informacao_a_pesquisar;LAMBDA(λ;JOIN("; "; UNIQUE(FILTER(coluna_a_retornar;coluna_a_pesquisar = λ)))))

=QRCODE(link)

Descrição: Insere um QRCode do link informado na célula. Baseado nesta API: https://goqr.me/api/

Te ajudei? Me manda um cafézinho ^^

[email protected]

Versão para uso como função nomeada

Nome da função:

QRCODE

Marcadores de posição de argumentos::

link

Definição da fórmula:

=IMAGE("https://api.qrserver.com/v1/create-qr-code/?size=1000x1000&data="&ENCODEURL(link))

=REMOVER_ACENTOS(texto_a_remover_acentos)

Descrição: Substitui os principais acentos da string para caracteres não-acentuados.

Te ajudei? Me manda um cafézinho ^^

[email protected]

Versão para uso como função nomeada

Nome da função:

REMOVER_ACENTOS

Marcadores de posição de argumentos::

texto_a_remover_acentos

Definição da fórmula:

=REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(REGEXREPLACE(texto_a_remover_acentos;"(À|Á|Â|Ã|Ä|Å)";"A");"(À|Á|Â|Ã|Ä|Å)";"A");"(à|á|â|ã|ä|å)";"a");"Ç";"C");"ç";"c");"Ð";"D");"ð";"d");"(È|É|Ê|Ë)";"E");"(è|é|ê|ë)";"e");"(Ì|Í|Î|Ï)";"I");"(ì|í|î|ï)";"i");"Ñ";"N");"ñ";"n");"(Ò|Ó|Ô|Õ|Ö)";"O");"(ò|ó|ô|õ|ö)";"o");"Š";"S");"š";"s");"(Ù|Ú|Û|Ü)";"U");"(ù|ú|û|ü)";"u");"(Ÿ|Ý)";"Y");"(ý|ÿ)";"y");"Ž";"Z")

Ferramentas para Streamelements

Caso você não conheça, StreamElements é uma ferramenta com diversas funcionalidades para livestreams, e dentre elas, criar overlays e widgets personalizados que podem ou não interagir com os espectadores.

Compartilho aqui alguns dos meus miniprojetos para que você possa usar na sua live :3

Como instalar um widget

Nunca usou um widget customizado? Comece por aqui! Leia mais.

Texto+

Prévia do widget Este widget serve para criar uma caixa de texto customizada, com mais funções de personalização que a nativa do StreamElements, como possibilidade de adicionar fontes customizadas e contorno nas letras. Leia mais.

Fonte da web

Prévia do widget Este widget adiciona um site dentro do overlay do StreamElements, desde que ele tenha suporte para incorporação. Leia mais.

Slideshow

Prévia do widget Este widget serve para adicionar várias imagens que se alternam num tempo especificado. É bem útil para colocar logos se alternando no canto do overlay. Leia mais.

Soundboard

Este widget aciona um áudio quando um espectador chama um comando. Widget em desenvolvimento. Leia mais.

Instalando Widgets customizados no StreamElements

Caso você não conheça, StreamElements é uma ferramenta com diversas funcionalidades para livestreams, como criação de overlays, chatbot, e outras interações com o público. Apesar de ter alguns modelos prontos, é possível criar overlays, widgets e comandos personalizados para deixar a live mais interativa.

Aqui nesse site vão ficar todos as minhas criações dentro do StreamElements. Aqui abaixo você pode ver como instalar os widgets customizados.

Como instalar um widget

  1. Caso você ainda não tenha criado um overlay, acesse https://streamelements.com/dashboard/overlays e clique em "New overlay"

Imagem demonstrando o descrito no ponto acima

  1. Clique em "➕" > Static/Custom > Custom Widget.

Imagem demonstrando o descrito no ponto acima

  1. Vá em "🔧 Settings" e em "Open Editor".

Imagem demonstrando o descrito no ponto acima

  1. Será aberta uma janela com 5 abas: HTML, CSS, JS, Fields e Data.

Imagem demonstrando o descrito no ponto acima

Neste site, o código dos widgets também está organizado pelos mesmos nomes, portanto basta copiar e colar o código nessas abas. Se alguma das abas não foi especificada na sua página, deixe ela em branco. Depois, só clicar em "Done".

Texto+

Prévia do widget

Este widget serve para criar uma caixa de texto customizada, com mais funções de personalização que a nativa do StreamElements, como possibilidade de adicionar fontes customizadas e contorno nas letras.
Agradecimentos especiais para a Dabs, do Pra Quem Gosta, por me permitir publicar este widget que eu fiz pra uso dela ^^

Te ajudei? Me manda um cafézinho ^^

[email protected]

Índice:

Guia de configuração

Antes de começar, se você não sabe instalar um widget no StreamElements, veja aqui como instalar.

Mensagem

Nesse campo você vai digitar o texto que vai aparecer na tela. Entretanto, uma das funções do Texto+ é permitir alterar esse texto temporariamente direto pelo OBS Studio, pela função de interagir. Assim que o overlay for atualizado, o texto voltará ao conteúdo original.

Um exemplo de caso de uso seria:
Caso você receba convidados com frequência no seu canal, você pode alterar rapidamente o nome que está no overlay, sem logar no StreamElements.

Fonte

Aqui você escolhe qual a fonte do texto. Caso você tenha um arquivo de fonte baixado e queira utilizar, renomeie a extensão do arquivo para ".mp3" e carregue como se fosse um áudio.

Formatação

Aqui você configura a formatação do seu texto, como o nível do negrito, tamanho, alinhamento, cor, entre outros.

Contorno

Aqui você configura se seu texto terá algum contorno em volta, e a cor. Não recomendo utilizar contornos muito grandes, pois as bordas tendem a ficar levemente quebradas.

Códigos

Veja aqui como instalar um widget customizado no StreamElements.

HTML

<body
    data-customFontCheckbox="{{customFontCheckbox}}"
    data-italic="{{fontItalic}}"
    data-strokeSize="{{strokeSize}}"
    >
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family={{googleFont}}:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">

    <textarea id="main" value="{{message}}" oninput="txtChange(this)">{{message}}</textarea>
    <textarea id="border" value="{{message}}">{{message}}</textarea>

</body>

CSS

:root {
    --fontWeigth: {{fontWeigth}};
    --fontColor: {{fontColor}};
    --fontSize: {{fontSize}}px;
    --googleFont: '{{googleFont}}';
    --textAlign: {{textAlign}};
    --lineHeight: {{lineHeight}};
    --strokeSize: {{strokeSize}};
    --strokeColor: {{strokeColor}};
}

@font-face {
    font-family: customFont;
    src: url("{{customFontLink}}")
}

body {
    position: relative;
    overflow: hidden;
    margin: 0;
    padding: 0;
}

textarea {
    position: absolute;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: calc(var(--strokeSize) * var(--fontSize) * 0.005);

    font-size: var(--fontSize);
    font-family: var(--googleFont);
    line-height: var(--lineHeight);
    text-align: var(--textAlign);
    font-weight: var(--fontWeigth);
    color: var(--fontColor);

    background: none;
    border: none;
    resize: none;
    overflow: hidden;
    outline: 0;

    &::placeholder {
        color: var(--fontColor);
        opacity: 100%!important;
    }

    &#main {
        z-index: 10;
    }

    &#border {
        z-index: 1;
        text-shadow: 
            calc(var(--strokeSize) * var(--fontSize) * -0.005) calc(var(--strokeSize) * var(--fontSize) * -0.005) 0 var(--strokeColor),
            0 calc(var(--strokeSize) * var(--fontSize) * -0.005) 0 var(--strokeColor),
            calc(var(--strokeSize) * var(--fontSize) * 0.005) calc(var(--strokeSize) * var(--fontSize) * -0.005) 0 var(--strokeColor),
            calc(var(--strokeSize) * var(--fontSize) * 0.005) 0 0 var(--strokeColor),
            calc(var(--strokeSize) * var(--fontSize) * 0.005) calc(var(--strokeSize) * var(--fontSize) * 0.005) 0 var(--strokeColor),
            0 calc(var(--strokeSize) * var(--fontSize) * 0.005) 0 var(--strokeColor),
            calc(var(--strokeSize) * var(--fontSize) * -0.005) calc(var(--strokeSize) * var(--fontSize) * 0.005) 0 var(--strokeColor),
            calc(var(--strokeSize) * var(--fontSize) * -0.005) 0 0 var(--strokeColor);
    }
}

body[data-customFontCheckbox="true"] textarea {
    font-family: customFont;
}

body[data-italic="true"] textarea {
    font-style: italic;
}

body[data-strokeSize="0"] #border {
    display: none;
}

Javascript

function txtChange(html) {
    document.querySelector('#border').value = html.value
}

Fields

{
    "credits1": {
        "type": "checkbox",
        "value": true,
        "label": "Detalhes de como fazer a configuração podem ser encontrados no site toolbox.danifluffy.dev :)",
        "group": "Texto+ | DaniFluffyCat ^^"
    },
    "credits2": {
        "type": "text",
        "label": "Você pode copiar o link abaixo:",
        "value": "toolbox.danifluffy.dev",
        "group": "Texto+ | DaniFluffyCat ^^"
    },
    "message": {
        "group": "Mensagem",
        "type": "text",
        "label": "Mensagem",
        "value": "Seu texto vem aqui"
    },
    "googleFont": {
        "group": "Fonte",
        "type": "googleFont",
        "label": "Selecione a fonte:",
        "value": "Roboto"
    },
    "customFontCheckbox": {
        "group": "Fonte",
        "type": "checkbox",
        "label": "Usar fonte customizada"
    },
    "customFontLink": {
        "group": "Fonte",
        "type": "sound-input",
        "label": "Carregue a fonte customizada (Renomeie a extensão para '.mp3'):"
    },
    "fontWeigth": {
        "group": "Formatação",
        "type": "slider",
        "label": "Nivel de negrito:",
        "value": 300,
        "min": 100,
        "max": 800,
        "step": 100
    },
    "fontItalic": {
        "group": "Formatação",
        "type": "checkbox",
        "label": "Itálico"
    },
    "fontColor": {
        "group": "Formatação",
        "type": "colorpicker",
        "label": "Cor",
        "value": "#000000"
    },
    "fontSize": {
        "group": "Formatação",
        "type": "number",
        "label": "Tamanho:",
        "value": 24,
        "min": 0,
        "max": 1000,
        "step": 1
    },
    "textAlign": {
        "group": "Formatação",
        "type": "dropdown",
        "label": "Alinhamento do texto:",
        "value": "center",
        "options": {
            "left": "Alinhar à esquerda",
            "center": "Alinhar ao centro",
            "right": "Alinhar à direita",
            "justify": "Justificar"
        }
    },
    "lineHeight": {
        "group": "Formatação",
        "type": "number",
        "label": "Altura da linha:",
        "value": 1,
        "min": 0,
        "max": 5,
        "step": 0.1
    },
    "strokeSize": {
        "group": "Contorno",
        "type": "slider",
        "label": "Tamanho do contorno:",
        "value": 0,
        "min": 0,
        "max": 10,
        "step": 0.1
    },
    "strokeColor": {
        "group": "Contorno",
        "type": "colorpicker",
        "label": "Cor do contorno:",
        "value": "#000000"
    }
}

Te ajudei? Me manda um cafézinho ^^

[email protected]

Fonte da web

Demonstração do widget

Este widget adiciona um site dentro do overlay do StreamElements, desde que ele tenha suporte para incorporação. No exemplo acima, este widget é usado para inserir uma webcam do OBS.ninja diretamente no overlay.

Agradecimentos especiais para a Dabs, do Pra Quem Gosta, por me permitir publicar este widget que eu fiz pra uso dela ^^

Te ajudei? Me manda um cafézinho ^^

[email protected]

Índice:

Guia de configuração

Antes de começar, se você não sabe instalar um widget no StreamElements, veja aqui como instalar.

URL

Aqui você irá definir qual site será exibido no widget.

Vale o aviso:

Para exibir um site por esse widget, o site em questão precisa permitir incorporação. Normalmente os sites que permitem tem uma opção chamada "Embed" ou "Incorporar" próxima a alguma opção de compartilhamento.
Isso não é uma limitação do widget ou do StreamElements, mas é parte das normas de segurança de iFrame, e não conheço nenhuma forma de contornar isso.
Se o site que você deseja inserir não suportar incorporação, este widget não carregará. Nesse caso, você deve inseri-lo diretamente como uma fonte de navegador no OBS Studio.

Recortar

Como pode ser que o site que você irá mostrar tenha mais informações que o necessário, aqui você pode cortar as bordas para focar no elemento importante.

Códigos

Veja aqui como instalar um widget customizado no StreamElements.

HTML

<body style="
    --cropTop: {{cropTop}}px;
    --cropBottom: {{cropBottom}}px;
    --cropLeft: {{cropLeft}}px;
    --cropRight: {{cropRight}}px;
">

    <div class="cutcontainer">
        <div class="placeholder" active="{{placeholderActive}}">
            <h2>Placeholder</h2>
            <p>Aqui irá ficar o site de URL:</p>
            <code>{{URL}}</code>
            <br>
            <p>Lembre de desativar o placeholder antes de salvar.</p>
        </div>
        <iframe src="{{URL}}" frameborder="0" scrolling="no" allowtransparency="true"></iframe>
    </div>

</body>

CSS

* {
    margin: 0;
    padding: 0;
    overflow: hidden;
}

iframe {
    width: 100vw;
    height: 100vh;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 0;
    background-color: transparent;
}

.placeholder {
    width: 100vw;
    height: 100vh;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;

    color: white;
    background-color: #5d22b5;
    box-shadow: inset 0 0 0 10px #2b1053;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

    font-family: Arial, Helvetica, sans-serif;
}

.placeholder h2 {
    margin-bottom: 10px;
    ;
}

[active='false'] {
    display: none !important;
}
body {
    -webkit-mask-image: linear-gradient(0deg,
            transparent 0px,
            transparent var(--cropBottom),
            black var(--cropBottom),
            black calc(100% - var(--cropTop)),
            transparent calc(100% - var(--cropTop)),
            transparent 100%);
    mask-image: linear-gradient(0deg,
            transparent 0px,
            transparent var(--cropBottom),
            black var(--cropBottom),
            black calc(100% - var(--cropTop)),
            transparent calc(100% - var(--cropTop)),
            transparent 100%);
}

.cutcontainer {

    width: 100vw;
    height: 100vh;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;

    -webkit-mask-image: linear-gradient(90deg,
        transparent 0px,
        transparent var(--cropLeft),
        black var(--cropLeft),
        black calc(100% - var(--cropRight)),
        transparent calc(100% - var(--cropRight)),
        transparent 100%);

    mask-image: linear-gradient(90deg,
        transparent 0px,
        transparent var(--cropLeft),
        black var(--cropLeft),
        black calc(100% - var(--cropRight)),
        transparent calc(100% - var(--cropRight)),
        transparent 100%)
}

JS

// Vazio

Fields

{
    "credits1": {
        "type": "checkbox",
        "value": true,
        "label": "Detalhes de como fazer a configuração podem ser encontrados no site toolbox.danifluffy.dev :)",
        "group": "Fonte da Web | DaniFluffyCat ^^"
    },
    "credits2": {
        "type": "text",
        "label": "Você pode copiar o link abaixo:",
        "value": "toolbox.danifluffy.dev",
        "group": "Fonte da Web | DaniFluffyCat ^^"
    },
    "URL": {
        "group": "URL",
        "type": "text",
        "label": "URL",
        "value": "URL"
    },
    "placeholderActive": {
        "group": "URL",
        "type": "checkbox",
        "value": true,
        "label": "Exibir placeholder"
    },
    "cropTop": {
        "group": "Recortar",
        "type": "number",
        "label": "Cima",
        "value": 0,
        "min": 0,
        "max": 4000,
        "step": 1
    },
    "cropBottom": {
        "group": "Recortar",
        "type": "number",
        "label": "Baixo",
        "value": 0,
        "min": 0,
        "max": 4000,
        "step": 1
    },
    "cropLeft": {
        "group": "Recortar",
        "type": "number",
        "label": "Esquerda",
        "value": 0,
        "min": 0,
        "max": 4000,
        "step": 1
    },
    "cropRight": {
        "group": "Recortar",
        "type": "number",
        "label": "Direita",
        "value": 0,
        "min": 0,
        "max": 4000,
        "step": 1
    }
}

Te ajudei? Me manda um cafézinho ^^

[email protected]

Shoutout para YouTube

Demonstração do widget Arte original: Cosmic Wolf

Este código ainda está em fase de testes.

Sinta-se á vontade para testar, mas esteja ciente que podem conter bugs. Use por sua conta e risco.

Use um comando de destaque para chamar atenção dos espectadores para uma pessoa importante no seu chat, como um raider, e convidar seu chat para conhecer o canal da pessoa!

Agradecimentos especiais para a Cosmic Wolf, por me permitir publicar este widget que eu fiz pra uso dela ^^

Te ajudei? Me manda um cafézinho ^^

[email protected]

Índice:

  1. Introdução
  2. Instalando o script no Google Drive
  3. Configurando o comando no Chatbot
  4. Configurando o widget

Introdução

Na Twitch, este comando é conhecido como /shoutout ou !s2, e é bem mais simples de se implementar, já que envolve simplesmente criar um comando que escreve twitch.tv/usuárioMarcado. Esse método não é possível no YouTube, já que o nome que aparece no chat não é seu nome de usuário.

Sendo assim, eu desenvolvi uma abordagem diferente que usa os dados que um widget tem do chat para enviar o link do usuário destacado para o chat! Entretanto, este é um código mais complexo que um widget normal, e ele se divide em 3 partes:

  • Um arquivo de script no seu Google Drive;
  • Um comando no chatbot do StreamElements;
  • Um widget do StreamElements que precisa estar ativo no seu OBS.

Parte 1: Instalando o script no Google Drive

Abra o seu Google Drive e vá em ➕ Novo > Mais > Script do Google Apps.
Você pode criar esse script em qualquer pasta do seu Google Drive, desde não corra o risco de você deletá-lo por acidente. Vale inclusive renomear depois o arquivo para você não o perder :)

Cole o código abaixo na janela que se abrir:

/** Função acionada quando a API é chamada.
 * @param {'store'|'find'} e.parameter.action - Ação a ser executada pela API
 * @param {string} e.parameter.username - Nome do usuário a executar a ação
 * @param {string} e.parameter.message - Mensagem a armazenar, obrigatório apenas caso action = store
 */
function doGet(e) {
  switch (e.parameter.action) {                                                     // A depender da ação requisitada, executar
    case 'store':                                                                   // Caso seja para armazenar:
      store(decodeURI(e.parameter.username), decodeURI(e.parameter.message));           // Armazenar URL
      return ContentService.createTextOutput(JSON.stringify({ status: 'stored' }));     // Retornar status de sucesso
    case 'find':                                                                    // Caso seja para encontrar:
      return ContentService.createTextOutput(find(e.parameter.username));               // Retornar URL
  }
}

/** Armazena dados do usuário na memória do GAS.
 * @param {string} username - Nome do usuário a executar a ação
 * @param {string} message - Mensagem a armazenar
 */
function store(username, message) {
  PropertiesService.getScriptProperties().setProperty(username, message)    // Armazena URL nas propriedades
}

/** Obter dados do usuário na memória do GAS.
 * @param {string} username - Nome do usuário a executar a ação
 */
function find(username) {
  let url = null                                                                        // Declara variável de URL
  for (let tries = 0; tries < 10; tries++) {                                            // Tenta achar o URL 10x  
    Utilities.sleep(3000);                                                                  // Espera 3seg
    url = PropertiesService.getScriptProperties().getProperty(username.replaceAll("@",""))  // Obtém URL das propriedades
    if (url != null) break;                                                                 // Se URL encontrada, sair do loop
  }
  if (url == null) return ""                                                            // Se URL não encontrada, retornar vazio
  return url                                                                            // Retornar URL
}

Clique em Implantar > Nova implantação.
Na nova janela, clique em ⚙️ > App da Web.
A opção "Executar como" deve estar marcada como "Eu", e "Quem pode acessar" como "Qualquer pessoa". Depois disso, clique em Implantar.

Copie o URL do App da Web e guarde temporariamente num Bloco de Notas; ele será importante nas próximas partes.

Parte 2: Configurando o comando no Chatbot

Existem 2 comandos de destaques diferentes:

  • Um silencioso, que apenas mostra a mensagem no chat; e
  • Um que também mostra um alerta no widget.

No video do tutorial, vou utilizar shoutout como comando silencioso, e raid como comando de alerta, mas você pode personalizar para qual texto preferir, desde que você use a mesma configuração de comando no chatbot e no widget.

Acesse https://streamelements.com/dashboard/bot/commands/custom.

Clique em Add new command.

Em Command name, informe o nome do comando silencioso.

Em Response type, copie e cole o texto abaixo, substituindo [URL do Apps Script] pelo URL que você anotou na parte anterior.

${urlfetch [URL do Apps Script]?action=find&username=${1:}}

Vá em Advanced settings > Command aliases e informe o nome do comando de alerta.

E finalize clicando em Activate command.

Opcionalmente, você também pode definir o User level, caso queira restringir o uso do comando apenas para moderadores, por exemplo.

Parte 3: Configurando o widget

Agora nós vamos instalar o widget no overlay. Eu já fiz um tutorial ensinando a instalar um widget no StreamElements, que você pode ver clicando aqui. Depois de instalado, entraremos diretamente na configuração do widget em si.

O código que você deve instalar é o seguinte:

HTML

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family={{alert_googleFont}}" rel="stylesheet">

<div id="alert_container" data-fade="{{alert_fade}}">
    <img id="image" src="{{alert_image}}">
    <p id="alert_text" data-value="{{alert_text}}" data-shadow="{{alert_shadow}}" style="--text-color: {{alert_textcolor}}; --shoutout-text-color: {{alert_shoutouttextcolor}}"></p>
    <audio id="alert_audio" src="{{alert_audio}}"></audio>
</div>

CSS

body,
#alert_container {
    display: flex;
    align-items: center;
    flex-direction: column;

    margin: 0;
    padding: 0;
    overflow: hidden;
    font-family: "{{alert_googleFont}}", sans-serif;
}

#alert_container {
    opacity: 0%;
    transition: 500ms ease;

    &[data-show="true"] {
        opacity: 100%;
    }
}

#image {
    height: 66vh;
}

#alert_text {
    margin: 0;
    margin-top: 3vh;
    font-size: 10vh;
    font-weight: 800;
    text-align: center;
    color: var(--text-color);

    span {
        color: var(--shoutout-text-color);
    }

    &[data-shadow="true"] {
        filter: drop-shadow(0 0 1vh #00000050);
    }
}

#audios {
    display: none;
}

Javascript

// Definição de variáveis
let chatUsers = new Map()                           // Objeto para armazenar os dados de quem fala no chat
let await_start = true                              // Variável para aguardar a inicialização do código
let apiUrl = "{{api_url}}"                          // URL da API do Google Apps Script
let cmdSilent = "!{{cmd_silentCommand}}"            // Comando que chama o shoutout silenciosamente
let cmdAlert = "!{{cmd_alertCommand}}"              // Comando que chama o alerta de shoutout
let cmdText = "{{cmd_text}}"                        // Texto que o comando vai retornar
let cmdPermissionLevel = "{{cmd_permissionLevel}}"  // Nível de permissão para usar o comando



// Executar assim que iniciar
window.onload = () => {

    // Definição de variáveis
    let alert_textHTML = document.querySelector(`#alert_text`)

    // Insere os elementos de texto na descrição do alerta
    alert_textHTML.innerHTML = alert_textHTML.dataset.value
        .replaceAll("{username}", '<span id="username">USER</span>')

    // Aguardar 5seg antes de disparar alertas
    setTimeout(() => {
        await_start = false;
        console.log("Shoutout inicializado.")
    }, 5000)
}



// Executar quando receber um evento
window.addEventListener('onEventReceived', function (obj) {

    if (await_start) return             // Caso esteja aguardando ligar, ignorar
    let data = obj.detail.event.data    // Armazena dados do evento

    // Executa uma ação baseada em um evento
    switch (obj.detail.listener) {

        case "message":

            // Definição de variáveis
            let text = data.text                        // Armazena texto da mensagem
            let user = data.authorDetails               // Armazena usuário da mensagem 
            let userAccess = user.isChatOwner ? 2 :     // Usuário é dono do canal?
                user.isChatModerator ? 1 : 0            // Usuário é mod do canal?
            let command = /^![^\s]{0,}/.exec(text)      // Armazena comando
            let userCalled = /(?<=@).*$/.exec(text)     // Armazena usuário marcado

            // Armazena usuário na database interna
            chatUsers.set(user.displayName, { url: user.channelUrl, pict: data.avatar })

            // Se não há comando ou usuário marcado, encerrar execução
            if (command == null || userCalled == null) return

            // Se há um comando do shoutout, enviar usuário para a API
            if (command[0] == cmdAlert || command[0] == cmdSilent) storeInAPI(userCalled[0])

            // Se o comando de alerta foi chamado e usuário tem permissão, mostrar alerta
            if (command[0] == cmdAlert && userAccess - cmdPermissionLevel >= 0 ) showAlert(userCalled[0])

            // Encerrar execução
            return;

        case "event:test":
            showAlert("DaniFluffyTesty")  // Executa alerta de teste
            return;                             // Encerra execução
    }
});



// Envia informações do usuário para API
function storeInAPI(username) {
    
    // Declaração de variáveis
    let user = undefined
    let loopCount = 0

    // Executar loop
    let loop = setInterval(() => {
                            
        if (loopCount < 10) clearInterval(loop)     // Caso mais de 10 loops, encerrar loop
        loopCount++                                 // Acrescenta 1 ao contador do loop

        user = chatUsers.get(username)  // Procura o username
        if (user == undefined) return   // Caso não tenha achado o usuário, partir para próxima tentativa

        // Envia os dados para a API
        let message = cmdText.replaceAll("{username}", username).replaceAll("{url}", user.url)
        fetch(`${apiUrl}?action=store&username=${decodeURI(username)}&message=${decodeURI(message)}`)
        clearInterval(loop)
        
    }, 3000);
}



// Mostra alerta na tela
function showAlert(username) {

    // Declaração de variáveis
    let alertHTML = document.querySelector(`#alert_container`)
    let usernameHTML = document.querySelector(`#username`)
    let audioHTML = document.querySelector("#alert_audio")

    usernameHTML.textContent = username             // Definir nome de usuário
    audioHTML.play()                                // Tocar audio do alerta

    alertHTML.dataset.show = true                   // Exibir overlay
    setTimeout(() =>
        alertHTML.dataset.show = false,             // Oculta overlay
        alertHTML.dataset.fade * 1000               // Aguarda xx segundos para isso
    )
}

Fields

{
    "credits1": {
        "type": "checkbox",
        "value": true,
        "label": "Detalhes de como fazer a configuração podem ser encontrados no site toolbox.danifluffy.dev :)",
        "group": "Shoutout | DaniFluffyCat ^^"
    },
    "credits2": {
        "type": "text",
        "label": "Você pode copiar o link abaixo:",
        "value": "toolbox.danifluffy.dev",
        "group": "Shoutout | DaniFluffyCat ^^"
    },
    "important": {
        "type": "checkbox",
        "label": "⚠️IMPORTANTE: Este widget é composto por 3 componentes: uma API, um comando e o widget em si. Para saber como configurar, acesse o site.",
        "value": true,
        "group": "Shoutout | DaniFluffyCat ^^"
    },
    "api_url": {
        "type": "text",
        "label": "URL da API do Google Apps Script",
        "value": "https://script.google.com/macros/s/abc123/exec",
        "group": "API"
    },
    "alert_image": {
        "type": "image-input",
        "label": "Imagem do alerta",
        "group": "Alerta"
    },
    "alert_audio": {
        "type": "sound-input",
        "label": "Som do alerta",
        "group": "Alerta"
    },
    "alert_text": {
        "type": "text",
        "label": "Texto do alerta",
        "value": "Confira o canal de {username}!",
        "group": "Alerta"
    },
    "alert_googleFont": {
        "type": "googleFont",
        "label": "Selecione a fonte:",
        "value": "Roboto",
        "group": "Alerta"
    },
    "alert_textcolor": {
        "type": "colorpicker",
        "label": "Cor do texto",
        "value": "#ffffff",
        "group": "Alerta"
    },
    "alert_shoutouttextcolor": {
        "type": "colorpicker",
        "label": "Cor de destaque do texto",
        "value": "#cccc00",
        "group": "Alerta"
    },
    "alert_shadow": {
        "type": "checkbox",
        "label": "Sombra do texto",
        "value": false,
        "group": "Alerta"
    },
    "alert_fade": {
        "type": "number",
        "label": "Duração do alerta (em seg)",
        "value": 10,
        "min": 5,
        "max": 30,
        "step": 1,
        "group": "Alerta"
    },
    "alert_test": {
        "type": "button",
        "label": "Testar alerta",
        "group": "Alerta"
    },
    "cmd_silentCommand": {
        "type": "text",
        "label": "Comando silencioso de destaque",
        "value": "shoutout",
        "group": "Comando"
    },
    "cmd_alertCommand": {
        "type": "text",
        "label": "Comando para chamar o alerta",
        "value": "raid",
        "group": "Comando"
    },
    "cmd_text": {
        "type": "text",
        "label": "Texto do comando",
        "value": "Chat, confira o canal de {username}! {url}",
        "group": "Comando"
    },
    "cmd_permissionLevel": {
        "type": "dropdown",
        "label": "Quem pode usar o comando?",
        "value": "1",
        "options": {
          "0": "Todo mundo",
          "1": "Apenas moderador e dono do canal",
          "2": "Apenas dono do canal"
        },
        "group": "Comando"
      }
}

Configuração "API"

Aqui você deve colar o URL que você anotou na parte 1 do tutorial.

Configuração "Alerta"

Aqui você vai configurar como o alerta irá aparecer na sua live. Tem 2 pontos importantes para destacar:

  1. Se você quiser inserir um video ao invés de uma imagem, sugiro converter para GIF com uma ferramenta como o EzGif.
  2. Em Texto do alerta você deve escrever {username} onde irá aparecer o nome da pessoa destacada.

Configuração "Comando"

Aqui você vai configurar como o comando irá responder no seu chat. Destaco uma configuração importante no Texto do comando: você deve escrever {username} onde irá aparecer o nome da pessoa destacada, e {url} onde irá aparecer o link do canal da pessoa.

Lembre-se:

Para o comando funcionar corretamente, o overlay com este widget precisa estar ativo no seu OBS, mesmo para usar o comando silencioso.

Te ajudei? Me manda um cafézinho ^^

[email protected]

Slideshow

Demonstração do widget

Este widget serve para adicionar várias imagens que se alternam num tempo especificado. É bem útil para colocar logos se alternando no canto do overlay.

Agradecimentos especiais à Dabs, do Pra Quem Gosta, por me permitir publicar este widget que eu fiz pra uso dela ^^

Te ajudei? Me manda um cafézinho ^^

[email protected]

Índice:

Guia de configuração

Antes de começar, se você não sabe instalar um widget no StreamElements, veja aqui como instalar.

Transição

Controla o tempo que vai levar na troca de cada imagem, e o tempo da transição entre as duas imagens.

Imagens

Aqui você irá inserir todas as imagens que serão exibidas no widget. Se você quiser inserir um video, sugiro converter para GIF com uma ferramenta como o EzGif

Códigos

Veja aqui como instalar um widget customizado no StreamElements.

HTML

<body 
    style="--transitionSpeed: {{transitionSpeed}}ms"
    data-imagesurls="{{imagesURLs}}"
    data-transitioninterval="{{transitionInterval}}"
></body>

CSS

* {
    margin: 0;
    padding: 0;
    overflow: hidden;
}

.image {
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;

    position: absolute;
    width: 100vw;
    height: 100vh;

    transition: var(--transitionSpeed) ease-in-out;
    opacity: 0%;
}

[visible='true'] {
    opacity: 100%;
}

Javascript

window.onload = () => {

    // Cria o HTML das imagens
    let imagesURLs = document.body.dataset.imagesurls.split(",")
    imagesURLs.forEach(url => {
        document.body.insertAdjacentHTML("beforeend", `<div class="image" style="background-image: url('${url}');"></div>`)
    })

    // Obtém as variáveis para o loop de imagens
    let interval = parseInt(document.body.dataset.transitioninterval)
    let images = document.querySelectorAll(".image[style*='streamelements.com']")
    let counter = 0

    // Exibe a primeira imagem
    document.querySelector(".image[style*='streamelements.com']").setAttribute('visible', 'true')

    // Cria o loop
    setInterval(() => {
        if (counter == images.length) counter = 0
        images.forEach((e) => e.setAttribute('visible', 'false'))
        images[counter].setAttribute('visible', 'true')
        counter++
    }, interval);

}

Fields

{

    "credits1": {
        "type": "checkbox",
        "value": true,
        "label": "Detalhes de como fazer a configuração podem ser encontrados no site toolbox.danifluffy.dev :)",
        "group": "Slideshow | DaniFluffyCat ^^"
    },
    "credits2": {
        "type": "text",
        "label": "Você pode copiar o link abaixo:",
        "value": "toolbox.danifluffy.dev",
        "group": "Slideshow | DaniFluffyCat ^^"
    },
    "transitionSpeed": {
        "group": "Transição",
        "type": "slider",
        "label": "Velocidade da transição (ms)",
        "value": 200,
        "min": 50,
        "max": 2000,
        "step": 50
    },
    "transitionInterval": {
        "group": "Transição",
        "type": "slider",
        "label": "Tempo entre imagens (ms)",
        "value": 5000,
        "min": 500,
        "max": 60000,
        "step": 500
    },
    "imagesURLs": {
        "group": "Imagens",
        "type": "image-input",
        "multiple": true,
        "label": "Imagens"
    }
}

Te ajudei? Me manda um cafézinho ^^

[email protected]

Soundboard para StreamElements

Este widget ainda está em desenvolvimento!

Este código ainda não está documentado e está em fase de testes. Use por sua conta e risco.

Este é um widget que aciona um áudio quando um espectador chama um comando. Criei ele na intenção de ser um tipo de "SoundAlerts" que funciona em lives no YouTube.

Te ajudei? Me manda um cafézinho ^^

[email protected]

A fazer:

  • Bolar algum meio do comando retornar algum feedback no chat;
  • Documentar o widget.

HTML

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family={{alert_googleFont}}" rel="stylesheet">

<div id="alert_container" data-fade="{{alert_fade}}" data-globaltimeout="{{alert_globaltimeout}}" data-usertimeout="{{alert_usertimeout}}">
    <img id="image" src="{{alert_image}}">
    <p id="alert_text" data-value="{{alert_text}}" data-shadow="{{alert_shadow}}" style="--text-color: {{alert_textcolor}}; --shoutout-text-color: {{alert_shoutouttextcolor}}"></p>
</div>

<div id="audios">
    <audio id="audio-{{audio01_name}}" data-active="{{audio01_active}}" src="{{audio01_file}}"></audio>
    <audio id="audio-{{audio02_name}}" data-active="{{audio02_active}}" src="{{audio02_file}}"></audio>
    <audio id="audio-{{audio03_name}}" data-active="{{audio03_active}}" src="{{audio03_file}}"></audio>
    <audio id="audio-{{audio04_name}}" data-active="{{audio04_active}}" src="{{audio04_file}}"></audio>
    <audio id="audio-{{audio05_name}}" data-active="{{audio05_active}}" src="{{audio05_file}}"></audio>
    <audio id="audio-{{audio06_name}}" data-active="{{audio06_active}}" src="{{audio06_file}}"></audio>
    <audio id="audio-{{audio07_name}}" data-active="{{audio07_active}}" src="{{audio07_file}}"></audio>
    <audio id="audio-{{audio08_name}}" data-active="{{audio08_active}}" src="{{audio08_file}}"></audio>
    <audio id="audio-{{audio09_name}}" data-active="{{audio09_active}}" src="{{audio09_file}}"></audio>
    <audio id="audio-{{audio10_name}}" data-active="{{audio10_active}}" src="{{audio10_file}}"></audio>
    <audio id="audio-{{audio11_name}}" data-active="{{audio11_active}}" src="{{audio11_file}}"></audio>
    <audio id="audio-{{audio12_name}}" data-active="{{audio12_active}}" src="{{audio12_file}}"></audio>
    <audio id="audio-{{audio13_name}}" data-active="{{audio13_active}}" src="{{audio13_file}}"></audio>
    <audio id="audio-{{audio14_name}}" data-active="{{audio14_active}}" src="{{audio14_file}}"></audio>
    <audio id="audio-{{audio15_name}}" data-active="{{audio15_active}}" src="{{audio15_file}}"></audio>
    <audio id="audio-{{audio16_name}}" data-active="{{audio16_active}}" src="{{audio16_file}}"></audio>
    <audio id="audio-{{audio17_name}}" data-active="{{audio17_active}}" src="{{audio17_file}}"></audio>
    <audio id="audio-{{audio18_name}}" data-active="{{audio18_active}}" src="{{audio18_file}}"></audio>
    <audio id="audio-{{audio19_name}}" data-active="{{audio19_active}}" src="{{audio19_file}}"></audio>
    <audio id="audio-{{audio20_name}}" data-active="{{audio20_active}}" src="{{audio20_file}}"></audio>
    <audio id="audio-{{audio21_name}}" data-active="{{audio21_active}}" src="{{audio21_file}}"></audio>
    <audio id="audio-{{audio22_name}}" data-active="{{audio22_active}}" src="{{audio22_file}}"></audio>
    <audio id="audio-{{audio23_name}}" data-active="{{audio23_active}}" src="{{audio23_file}}"></audio>
    <audio id="audio-{{audio24_name}}" data-active="{{audio24_active}}" src="{{audio24_file}}"></audio>
    <audio id="audio-{{audio25_name}}" data-active="{{audio25_active}}" src="{{audio25_file}}"></audio>
    <audio id="audio-{{audio26_name}}" data-active="{{audio26_active}}" src="{{audio26_file}}"></audio>
    <audio id="audio-{{audio27_name}}" data-active="{{audio27_active}}" src="{{audio27_file}}"></audio>
    <audio id="audio-{{audio28_name}}" data-active="{{audio28_active}}" src="{{audio28_file}}"></audio>
    <audio id="audio-{{audio29_name}}" data-active="{{audio29_active}}" src="{{audio29_file}}"></audio>
    <audio id="audio-{{audio30_name}}" data-active="{{audio30_active}}" src="{{audio30_file}}"></audio>
    <audio id="audio-{{audio31_name}}" data-active="{{audio31_active}}" src="{{audio31_file}}"></audio>
    <audio id="audio-{{audio32_name}}" data-active="{{audio32_active}}" src="{{audio32_file}}"></audio>
    <audio id="audio-{{audio33_name}}" data-active="{{audio33_active}}" src="{{audio33_file}}"></audio>
    <audio id="audio-{{audio34_name}}" data-active="{{audio34_active}}" src="{{audio34_file}}"></audio>
    <audio id="audio-{{audio35_name}}" data-active="{{audio35_active}}" src="{{audio35_file}}"></audio>
    <audio id="audio-{{audio36_name}}" data-active="{{audio36_active}}" src="{{audio36_file}}"></audio>
    <audio id="audio-{{audio37_name}}" data-active="{{audio37_active}}" src="{{audio37_file}}"></audio>
    <audio id="audio-{{audio38_name}}" data-active="{{audio38_active}}" src="{{audio38_file}}"></audio>
    <audio id="audio-{{audio39_name}}" data-active="{{audio39_active}}" src="{{audio39_file}}"></audio>
    <audio id="audio-{{audio40_name}}" data-active="{{audio40_active}}" src="{{audio40_file}}"></audio>
    <audio id="audio-{{audio41_name}}" data-active="{{audio41_active}}" src="{{audio41_file}}"></audio>
    <audio id="audio-{{audio42_name}}" data-active="{{audio42_active}}" src="{{audio42_file}}"></audio>
    <audio id="audio-{{audio43_name}}" data-active="{{audio43_active}}" src="{{audio43_file}}"></audio>
    <audio id="audio-{{audio44_name}}" data-active="{{audio44_active}}" src="{{audio44_file}}"></audio>
    <audio id="audio-{{audio45_name}}" data-active="{{audio45_active}}" src="{{audio45_file}}"></audio>
    <audio id="audio-{{audio46_name}}" data-active="{{audio46_active}}" src="{{audio46_file}}"></audio>
    <audio id="audio-{{audio47_name}}" data-active="{{audio47_active}}" src="{{audio47_file}}"></audio>
    <audio id="audio-{{audio48_name}}" data-active="{{audio48_active}}" src="{{audio48_file}}"></audio>
    <audio id="audio-{{audio49_name}}" data-active="{{audio49_active}}" src="{{audio49_file}}"></audio>
    <audio id="audio-{{audio50_name}}" data-active="{{audio50_active}}" src="{{audio50_file}}"></audio>
</div>

CSS

body,
#alert_container {
    display: flex;
    align-items: center;
    flex-direction: column;

    margin: 0;
    padding: 0;
    overflow: hidden;
    font-family: "{{alert_googleFont}}", sans-serif;
}

#alert_container {
    opacity: 0%;
    transition: 500ms ease;

    &[data-show="true"] {
        opacity: 100%;
    }
}

#image {
    height: 66vh;
}

#alert_text {
    margin: 0;
    margin-top: 3vh;
    font-size: 10vh;
    font-weight: 800;
    text-align: center;
    color: var(--text-color);

    span {
        color: var(--shoutout-text-color);
    }

    &[data-shadow="true"] {
        filter: drop-shadow(0 0 1vh #00000050);
    }
}

#audios {
    display: none;
}

Javascript

// Executar assim que iniciar

let await_start = true
window.onload = () => {

    let alertHTML = document.querySelector(`#alert_container`)
    let alert_textHTML = document.querySelector(`#alert_text`)

    // Insere os elementos de texto na descrição do alerta
    alert_textHTML.innerHTML = alert_textHTML.dataset.value
        .replaceAll("{username}", '<span id="username">USER</span>')
        .replaceAll("{audioname}", '<span id="audioname">AUDIO</span>')

    // Define o timeout global no objeto de timeout
    timeout.globalTimeout = parseInt(alertHTML.dataset.fade) + parseInt(alertHTML.dataset.globaltimeout)
    timeout.userTimeout = parseInt(alertHTML.dataset.fade) + parseInt(alertHTML.dataset.usertimeout)

    // Aguardar 5seg antes de disparar alertas
    setTimeout(() => {
        await_start = false;
        console.log("Soundboard inicializado.")
    }, 5000)
}



// Guarda os comandos dos áudios

let audioCmds = {
    audio01: "{{audio01_name}}", 
    audio02: "{{audio02_name}}",
    audio03: "{{audio03_name}}",
    audio04: "{{audio04_name}}",
    audio05: "{{audio05_name}}",
    audio06: "{{audio06_name}}",
    audio07: "{{audio07_name}}",
    audio08: "{{audio08_name}}",
    audio09: "{{audio09_name}}",
    audio10: "{{audio10_name}}",
    audio11: "{{audio11_name}}",
    audio12: "{{audio12_name}}",
    audio13: "{{audio13_name}}",
    audio14: "{{audio14_name}}",
    audio15: "{{audio15_name}}",
    audio16: "{{audio16_name}}",
    audio17: "{{audio17_name}}",
    audio18: "{{audio18_name}}",
    audio19: "{{audio19_name}}",
    audio20: "{{audio20_name}}",
    audio21: "{{audio21_name}}",
    audio22: "{{audio22_name}}",
    audio23: "{{audio23_name}}",
    audio24: "{{audio24_name}}",
    audio25: "{{audio25_name}}",
    audio26: "{{audio26_name}}",
    audio27: "{{audio27_name}}",
    audio28: "{{audio28_name}}",
    audio29: "{{audio29_name}}",
    audio30: "{{audio30_name}}",
    audio31: "{{audio31_name}}",
    audio32: "{{audio32_name}}",
    audio33: "{{audio33_name}}",
    audio34: "{{audio34_name}}",
    audio35: "{{audio35_name}}",
    audio36: "{{audio36_name}}",
    audio37: "{{audio37_name}}",
    audio38: "{{audio38_name}}",
    audio39: "{{audio39_name}}",
    audio40: "{{audio40_name}}",
    audio41: "{{audio41_name}}",
    audio42: "{{audio42_name}}",
    audio43: "{{audio43_name}}",
    audio44: "{{audio44_name}}",
    audio45: "{{audio45_name}}",
    audio46: "{{audio46_name}}",
    audio47: "{{audio47_name}}",
    audio48: "{{audio48_name}}",
    audio49: "{{audio49_name}}",
    audio50: "{{audio50_name}}"
}



// Executar quando receber um evento

window.addEventListener('onEventReceived', function (obj) {

    if (await_start) return     // Caso esteja aguardando ligar, ignorar

    // Executa uma ação baseada em um evento
    switch (obj.detail.listener) {

        case "message":
            let message = obj.detail.event.data.text                            // Armazena mensagem
            let nick = obj.detail.event.data.nick                               // Armazena nick
            if (message.indexOf("!") == -1) return                              // Caso não seja comando, ignorar
            message = message.replaceAll("!", "")                               // Remove símbolo de comando
            if (document.querySelector(`#audio-${message}`) == null) return     // Caso audio não exista, ignorar
            showAlert(nick, message)                                            // Exibir alerta
            return;

        case "event:test":
            showAlert("DaniFluffyTesty", audioCmds[obj.detail.event.value], true)   // Executa alerta de teste
            return;
    }
});



/** Mostra o alerta */

function showAlert(username, audioname, isTest) {

    let alertHTML = document.querySelector(`#alert_container`)
    let audioHTML = document.querySelector(`#audio-${audioname}`)
    let usernameHTML = document.querySelector(`#username`)
    let audionameHTML = document.querySelector(`#audioname`)

    if (!timeout.run(username, isTest)) return      // Caso timeout não expirado, ignorar
    if (audioHTML.dataset.active != "true") return  // Caso audio inativo, ignorar

    usernameHTML.textContent = username             // Definir nome de usuário
    audionameHTML.textContent = audioname           // Definir nome do áudio
    audioHTML.play()                                // Tocar audio    

    alertHTML.dataset.show = true                   // Exibir overlay
    setTimeout(() =>
        alertHTML.dataset.show = false,             // Oculta overlay
        alertHTML.dataset.fade * 1000               // Aguarda xx segundos para isso
    )
}



/** Gerencia os timeouts*/

let timeout = {
    globalTimeout: 0,
    userTimeout: 0,
    userData: {},
    lastRun: new Date().getTime(),
    run: function (username, isTest) {
        if (isTest) return true
        if ((new Date().getTime()) < this.lastRun) return false               // Caso timeout global não tenha expirado, retornar false
        if ((new Date().getTime()) < this.userData[username]) return false    // Caso timeout de user não tenha expirado, retornar false

        this.lastRun = (new Date().getTime()) + this.globalTimeout * 1000           // Atualiza última execução
        this.userData[username] = (new Date().getTime()) + this.userTimeout * 1000  // Atualiza última execução do usuário
        return true
    }
}

Fields

{
    "credits1": {
        "type": "checkbox",
        "value": true,
        "label": "Detalhes de como fazer a configuração podem ser encontrados no site toolbox.danifluffy.dev :)",
        "group": "Soundboard | DaniFluffyCat ^^"
    },
    "credits2": {
        "type": "text",
        "label": "Você pode copiar o link abaixo:",
        "value": "toolbox.danifluffy.dev",
        "group": "Soundboard | DaniFluffyCat ^^"
    },
    "alert_image": {
        "type": "image-input",
        "label": "Imagem do alerta",
        "group": "Configs. alerta"
    },
    "alert_text": {
        "type": "text",
        "label": "Texto do alerta",
        "value": "{username} tocou {audioname}",
        "group": "Configs. alerta"
    },
    "alert_googleFont": {
        "type": "googleFont",
        "label": "Selecione a fonte:",
        "value": "Roboto",
        "group": "Configs. alerta"
    },
    "alert_textcolor": {
        "type": "colorpicker",
        "label": "Cor do texto",
        "value": "#ffffff",
        "group": "Configs. alerta"
    },
    "alert_shoutouttextcolor": {
        "type": "colorpicker",
        "label": "Cor de destaque do texto",
        "value": "#cccc00",
        "group": "Configs. alerta"
    },
    "alert_shadow": {
        "type": "checkbox",
        "label": "Sombra do texto",
        "value": false,
        "group": "Configs. alerta"
    },
    "alert_fade": {
        "type": "number",
        "label": "Duração do alerta (em seg)",
        "value": 10,
        "min": 5,
        "max": 30,
        "step": 1,
        "group": "Configs. alerta"
    },
    "alert_globaltimeout": {
        "type": "number",
        "label": "Tempo mínimo entre alertas (em seg)",
        "value": 10,
        "min": 5,
        "max": 999,
        "step": 1,
        "group": "Configs. alerta"
    },
    "alert_usertimeout": {
        "type": "number",
        "label": "Cooldown do usuário (em seg)",
        "value": 10,
        "min": 5,
        "max": 999,
        "step": 1,
        "group": "Configs. alerta"
    },
    "audio01_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 01"
    },
    "audio01_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 01"
    },
    "audio01_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 01"
    },
    "audio01_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio01",
        "group": "🎶 Áudio 01"
    },
    "audio02_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 02"
    },
    "audio02_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 02"
    },
    "audio02_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 02"
    },
    "audio02_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio02",
        "group": "🎶 Áudio 02"
    },
    "audio03_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 03"
    },
    "audio03_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 03"
    },
    "audio03_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 03"
    },
    "audio03_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio03",
        "group": "🎶 Áudio 03"
    },
    "audio04_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 04"
    },
    "audio04_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 04"
    },
    "audio04_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 04"
    },
    "audio04_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio04",
        "group": "🎶 Áudio 04"
    },
    "audio05_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 05"
    },
    "audio05_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 05"
    },
    "audio05_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 05"
    },
    "audio05_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio05",
        "group": "🎶 Áudio 05"
    },
    "audio06_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 06"
    },
    "audio06_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 06"
    },
    "audio06_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 06"
    },
    "audio06_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio06",
        "group": "🎶 Áudio 06"
    },
    "audio07_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 07"
    },
    "audio07_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 07"
    },
    "audio07_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 07"
    },
    "audio07_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio07",
        "group": "🎶 Áudio 07"
    },
    "audio08_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 08"
    },
    "audio08_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 08"
    },
    "audio08_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 08"
    },
    "audio08_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio08",
        "group": "🎶 Áudio 08"
    },
    "audio09_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 09"
    },
    "audio09_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 09"
    },
    "audio09_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 09"
    },
    "audio09_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio09",
        "group": "🎶 Áudio 09"
    },
    "audio10_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 10"
    },
    "audio10_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 10"
    },
    "audio10_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 10"
    },
    "audio10_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio10",
        "group": "🎶 Áudio 10"
    },
    "audio11_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 11"
    },
    "audio11_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 11"
    },
    "audio11_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 11"
    },
    "audio11_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio11",
        "group": "🎶 Áudio 11"
    },
    "audio12_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 12"
    },
    "audio12_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 12"
    },
    "audio12_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 12"
    },
    "audio12_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio12",
        "group": "🎶 Áudio 12"
    },
    "audio13_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 13"
    },
    "audio13_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 13"
    },
    "audio13_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 13"
    },
    "audio13_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio13",
        "group": "🎶 Áudio 13"
    },
    "audio14_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 14"
    },
    "audio14_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 14"
    },
    "audio14_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 14"
    },
    "audio14_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio14",
        "group": "🎶 Áudio 14"
    },
    "audio15_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 15"
    },
    "audio15_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 15"
    },
    "audio15_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 15"
    },
    "audio15_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio15",
        "group": "🎶 Áudio 15"
    },
    "audio16_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 16"
    },
    "audio16_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 16"
    },
    "audio16_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 16"
    },
    "audio16_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio16",
        "group": "🎶 Áudio 16"
    },
    "audio17_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 17"
    },
    "audio17_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 17"
    },
    "audio17_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 17"
    },
    "audio17_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio17",
        "group": "🎶 Áudio 17"
    },
    "audio18_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 18"
    },
    "audio18_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 18"
    },
    "audio18_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 18"
    },
    "audio18_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio18",
        "group": "🎶 Áudio 18"
    },
    "audio19_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 19"
    },
    "audio19_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 19"
    },
    "audio19_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 19"
    },
    "audio19_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio19",
        "group": "🎶 Áudio 19"
    },
    "audio20_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 20"
    },
    "audio20_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 20"
    },
    "audio20_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 20"
    },
    "audio20_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio20",
        "group": "🎶 Áudio 20"
    },
    "audio21_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 21"
    },
    "audio21_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 21"
    },
    "audio21_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 21"
    },
    "audio21_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio21",
        "group": "🎶 Áudio 21"
    },
    "audio22_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 22"
    },
    "audio22_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 22"
    },
    "audio22_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 22"
    },
    "audio22_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio22",
        "group": "🎶 Áudio 22"
    },
    "audio23_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 23"
    },
    "audio23_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 23"
    },
    "audio23_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 23"
    },
    "audio23_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio23",
        "group": "🎶 Áudio 23"
    },
    "audio24_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 24"
    },
    "audio24_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 24"
    },
    "audio24_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 24"
    },
    "audio24_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio24",
        "group": "🎶 Áudio 24"
    },
    "audio25_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 25"
    },
    "audio25_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 25"
    },
    "audio25_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 25"
    },
    "audio25_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio25",
        "group": "🎶 Áudio 25"
    },
    "audio26_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 26"
    },
    "audio26_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 26"
    },
    "audio26_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 26"
    },
    "audio26_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio26",
        "group": "🎶 Áudio 26"
    },
    "audio27_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 27"
    },
    "audio27_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 27"
    },
    "audio27_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 27"
    },
    "audio27_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio27",
        "group": "🎶 Áudio 27"
    },
    "audio28_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 28"
    },
    "audio28_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 28"
    },
    "audio28_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 28"
    },
    "audio28_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio28",
        "group": "🎶 Áudio 28"
    },
    "audio29_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 29"
    },
    "audio29_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 29"
    },
    "audio29_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 29"
    },
    "audio29_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio29",
        "group": "🎶 Áudio 29"
    },
    "audio30_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 30"
    },
    "audio30_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 30"
    },
    "audio30_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 30"
    },
    "audio30_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio30",
        "group": "🎶 Áudio 30"
    },
    "audio31_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 31"
    },
    "audio31_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 31"
    },
    "audio31_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 31"
    },
    "audio31_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio31",
        "group": "🎶 Áudio 31"
    },
    "audio32_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 32"
    },
    "audio32_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 32"
    },
    "audio32_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 32"
    },
    "audio32_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio32",
        "group": "🎶 Áudio 32"
    },
    "audio33_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 33"
    },
    "audio33_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 33"
    },
    "audio33_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 33"
    },
    "audio33_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio33",
        "group": "🎶 Áudio 33"
    },
    "audio34_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 34"
    },
    "audio34_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 34"
    },
    "audio34_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 34"
    },
    "audio34_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio34",
        "group": "🎶 Áudio 34"
    },
    "audio35_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 35"
    },
    "audio35_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 35"
    },
    "audio35_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 35"
    },
    "audio35_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio35",
        "group": "🎶 Áudio 35"
    },
    "audio36_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 36"
    },
    "audio36_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 36"
    },
    "audio36_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 36"
    },
    "audio36_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio36",
        "group": "🎶 Áudio 36"
    },
    "audio37_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 37"
    },
    "audio37_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 37"
    },
    "audio37_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 37"
    },
    "audio37_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio37",
        "group": "🎶 Áudio 37"
    },
    "audio38_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 38"
    },
    "audio38_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 38"
    },
    "audio38_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 38"
    },
    "audio38_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio38",
        "group": "🎶 Áudio 38"
    },
    "audio39_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 39"
    },
    "audio39_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 39"
    },
    "audio39_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 39"
    },
    "audio39_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio39",
        "group": "🎶 Áudio 39"
    },
    "audio40_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 40"
    },
    "audio40_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 40"
    },
    "audio40_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 40"
    },
    "audio40_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio40",
        "group": "🎶 Áudio 40"
    },
    "audio41_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 41"
    },
    "audio41_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 41"
    },
    "audio41_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 41"
    },
    "audio41_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio41",
        "group": "🎶 Áudio 41"
    },
    "audio42_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 42"
    },
    "audio42_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 42"
    },
    "audio42_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 42"
    },
    "audio42_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio42",
        "group": "🎶 Áudio 42"
    },
    "audio43_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 43"
    },
    "audio43_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 43"
    },
    "audio43_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 43"
    },
    "audio43_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio43",
        "group": "🎶 Áudio 43"
    },
    "audio44_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 44"
    },
    "audio44_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 44"
    },
    "audio44_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 44"
    },
    "audio44_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio44",
        "group": "🎶 Áudio 44"
    },
    "audio45_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 45"
    },
    "audio45_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 45"
    },
    "audio45_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 45"
    },
    "audio45_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio45",
        "group": "🎶 Áudio 45"
    },
    "audio46_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 46"
    },
    "audio46_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 46"
    },
    "audio46_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 46"
    },
    "audio46_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio46",
        "group": "🎶 Áudio 46"
    },
    "audio47_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 47"
    },
    "audio47_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 47"
    },
    "audio47_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 47"
    },
    "audio47_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio47",
        "group": "🎶 Áudio 47"
    },
    "audio48_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 48"
    },
    "audio48_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 48"
    },
    "audio48_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 48"
    },
    "audio48_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio48",
        "group": "🎶 Áudio 48"
    },
    "audio49_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 49"
    },
    "audio49_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 49"
    },
    "audio49_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 49"
    },
    "audio49_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio49",
        "group": "🎶 Áudio 49"
    },
    "audio50_active": {
        "type": "checkbox",
        "label": "Ativar áudio",
        "value": false,
        "group": "🎶 Áudio 50"
    },
    "audio50_name": {
        "type": "text",
        "label": "Comando para acionar áudio",
        "value": "Digite o comando aqui",
        "group": "🎶 Áudio 50"
    },
    "audio50_file": {
        "type": "sound-input",
        "label": "Arquivo de áudio",
        "group": "🎶 Áudio 50"
    },
    "audio50_test": {
        "type": "button",
        "label": "Testar alerta",
        "value": "audio50",
        "group": "🎶 Áudio 50"
    }
}

Te ajudei? Me manda um cafézinho ^^

[email protected]