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]