Criar Automations personalizadas

:information_source: Este é um rascunho e pode precisar de trabalho adicional.

Vocabulário

  • trigger: representa o nome do gatilho, por exemplo: user_added_to_group
  • triggerable: representa a lógica de código associada a um gatilho, por exemplo: triggers/user_added_to_group_.rb
  • script: representa o nome do script, por exemplo: send_pms
  • scriptable: representa a lógica de código associada a um script, por exemplo: scripts/send_pms.rb

API de Plugin

add_automation_scriptable(name, &block)
add_automation_triggerable(name, &block)

API de Scriptable

field

field :name, component: permite adicionar um valor personalizável na interface do usuário de sua automação.

Lista de componentes válidos:

# foo deve ser único e representa o nome do seu campo.

field :foo, component: :text # gera uma entrada de texto
field :foo, component: :list # gera uma entrada de texto de múltipla seleção onde os usuários podem preencher valores
field :foo, component: :choices, extra: { content: [ {id: 1, name: 'your.own.i18n.key.path' } ] } # gera um combo-box com conteúdo personalizado
field :foo, component: :boolean # gera uma entrada de caixa de seleção
field :foo, component: :category # gera um seletor de categoria
field :foo, component: :group # gera um seletor de grupo
field :foo, component: :date_time # gera um seletor de data e hora
field :foo, component: :tags # gera um seletor de tags
field :foo, component: :user  # gera um seletor de usuário
field :foo, component: :pms  # permite criar um ou mais modelos de PM
field :foo, component: :categories  # permite selecionar zero ou mais categorias
field :foo, component: :key-value  # permite criar pares chave-valor
field :foo, component: :message  # permite compor uma PM com variáveis substituíveis
field :foo, component: :trustlevel  # permite selecionar um ou mais níveis de confiança
triggerables e triggerable!
# Permite definir a lista de triggerables permitidos para um script
triggerables %i[recurring]

# Permite forçar um triggerable para o seu script e também permite forçar algum estado nos campos
field :recurring, component: :boolean
triggerable! :recurring, state: { foo: false }
placeholders
# Permite marcar uma chave como substituível em textos usando a sintaxe de placeholder `%%sender%%`
placeholder :sender

Note que é responsabilidade do script fornecer valores para os placeholders e aplicar a substituição usando input = utils.apply_placeholders(input, { sender: 'bob' })

script

Este é o coração de uma automação e onde toda a lógica acontece.

# o contexto é enviado quando a automação é acionada e pode diferir bastante entre os gatilhos
script do |context, fields, automation|
end

Localização

Cada campo que você usar dependerá de chaves i18n e será nomeado de acordo com seu gatilho/script.

Por exemplo, um scriptable com este conteúdo:

field :post_created_edited, component: :category

Exigirá as seguintes chaves em client.en.yml:

en:
  js:
    discourse_automation:
      scriptables:
        post_created_edited:
          fields:
            restricted_category:
              label: Categoria
              description: Opcional, permite limitar a execução do gatilho a esta categoria

Note que a descrição é opcional aqui.


Este documento é controlado por versão - sugira alterações no github.

7 curtidas

Quando vi que este era um novo tópico, fiquei animado: pensei que mais detalhes tinham sido compartilhados! :laughing:

Como alguém que não programa em Ruby, mas está muito interessado em automação de fluxo de trabalho, eu esperava que pudesse entender um pouco mais através de exemplos…

:thinking:

Acho que terei que começar em Developing Discourse Plugins - Part 1 - Create a basic plugin:sweat_smile:

8 curtidas

Eu basicamente apenas cortei esta seção do tópico do plugin para que não parecesse que você precisava saber disso para usar os existentes. :slight_smile:

Concordo que seria ótimo se isso fosse um pouco mais passo a passo. Enviei um sinal para assistência da comunidade aqui para ver se alguém tem experiência com tais coisas: :crossed_fingers:

5 curtidas

Um exemplo de “hello world” seria legal.
Onde os scripts devem ser armazenados? Gostaria de experimentar um pouco.

4 curtidas

Eu acho que você pode escrever scripts personalizados usando o Chat GPT junto com este plugin.

Provavelmente o melhor lugar para começar a procurar é no script de automação que é adicionado ao plugin Data Explorer: discourse-data-explorer/plugin.rb at main · discourse/discourse-data-explorer · GitHub. Vale a pena também olhar os scripts e gatilhos existentes do plugin Automation: https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation

Como não há muita informação no Meta sobre como adicionar automações personalizadas, aqui está um exemplo de arquivo plugin.rb que adiciona um script para atualizar a preferência de e-mail de resumo de atividade de um usuário. O script pode ser acionado pelos gatilhos ‘user_added_to_group’ ou ‘user_removed_from_group’ do plugin Automation.

# frozen_string_literal: true

# name: automation-script-example
# about: An example of how to add a script to an automation
# version: 0.0.1
# authors: scossar

enabled_site_setting :automation_script_example_enabled

after_initialize do
  reloadable_patch do
    if defined?(DiscourseAutomation)
      DiscourseAutomation::Scriptable::USER_UPDATE_SUMMARY_EMAIL_OPTIONS =
        "user_update_summary_email_options"
      add_automation_scriptable(
        DiscourseAutomation::Scriptable::USER_UPDATE_SUMMARY_EMAIL_OPTIONS
      ) do

        field :email_digests, component: :boolean

        version 1
        triggerables [:user_added_to_group, :user_removed_from_group]

        script do |context, fields, automation|
          if automation.script == "user_update_summary_email_options" && (context["kind"] == "user_added_to_group" || context["kind"] == "user_removed_from_group")
            user_id = context["user"].id
            digest_option = fields.dig("email_digests", "value")
            user_option = UserOption.find_by(user_id: user_id)

            if (user_option)
              user_option.update(email_digests: digest_option)
            end
          end
        end
      end
    end
  end
end

O código completo do plugin está aqui: GitHub - scossar/automation-script-example: An example of how to add a custom script to the Discourse Automation plugin..

:warning: por favor, não use este código como está em um site de produção. Eu não tinha olhado o código de Automação antes desta noite. Se eu receber algum feedback sobre possíveis problemas com o código, atualizarei esta postagem e o repositório GitHub.

Editar: minha preocupação era como lidar melhor com o caso de múltiplos scripts de automação sendo acionados pelos gatilhos ‘user_added_to_group’ ou ‘user_removed_from_group’. A versão inicial do plugin estava verificando:

fields.has_key?("email_digests")

mas isso parecia um pouco instável. E se outro script fosse adicionado que também tivesse uma chave email_digests?

O código atualizado passa o parâmetro automation para o bloco de código e verifica:

automation.script == "user_update_summary_email_options"

Isso deve garantir que o script não seja executado para a automação errada.
… pensando um pouco mais, é improvável que o script possa ser acionado por uma automação para a qual não foi configurado :slight_smile:

7 curtidas

Eu também gostaria de saber isso - depois de criar um repositório como o do @simon, como ele é acessado pelo plugin?

Temos que fazer um fork de todo o plugin e colocá-lo junto com os existentes em https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation/scripts? Ou existe uma maneira mais elegante?

1 curtida

Você precisa instalá-lo como qualquer outro plugin do Discourse: Instalar Plugins no Discourse. Assim, você instalaria o plugin de Automação e seu plugin que adiciona os scripts personalizados. A razão pela qual funciona é por causa dos métodos definidos aqui: https://github.com/discourse/discourse-automation/blob/main/lib/plugin/instance.rb. No código de exemplo que postei acima, você verá que o script personalizado está sendo adicionado com uma chamada para add_automation_scriptable.

Observação: não instale a automação de exemplo do meu repositório do GitHub, apenas use-a como um exemplo de como estender o plugin de Automação. (Esqueci que havia vinculado a ele aqui e o atualizei para que funcione apenas com minha versão bifurcada do plugin Discourse Automation. O código que vinculei aqui ainda é válido: Create custom Automations - #6 by simon. Atualizarei o plugin automation-script-example o mais rápido possível para que funcione sem as alterações que fiz em minha versão bifurcada do plugin Automation.)

Minha preocupação era infundada. Esta condição não é necessária:

if automation.script == "user_update_summary_email_options" && (context["kind"] == "user_added_to_group" || context["kind"] == "user_removed_from_group")

Atualizarei o exemplo em breve.

4 curtidas

Estou correto em entender que automações personalizadas exigem uma instalação auto-hospedada (ou, de outra forma, acesso direto ao backend ao sistema de arquivos onde o Discourse está instalado)?

2 curtidas

Sim, no entanto, somos muito receptivos à fusão de novos scripts de automação na compilação da comunidade, o que você está pensando em construir?

6 curtidas

Especificamente, estamos procurando uma maneira de substituir uma string específica em posts (não nos importamos muito com a sintaxe exata, mas algo como o texto simples @ref `Random.rand!` ) por um link formatado como Random.rand!. Pesquisar o URL exato é um processo complicado com dezenas de milhares de alvos possíveis que é completamente inviável com regexes (como faz o Auto linkify words/watched words) e muito mais fácil com um ambiente semelhante a um plugin completo em Turing… então eu estava curioso se as automações poderiam fazer isso.

Então eu estava procurando por uma ação pós-edição, um tanto semelhante ao que o usuário @system faz quando você cita o post anterior inteiro (veja aqui). Seria um script de “Editar post” que seria acionado em “Post criado” (ou talvez após o post cozido)… mas suponho que o framework de automações não permitiria uma ação “pós-edição” tão geral. Acho que seria uma automação personalizada bastante específica de link para a documentação do Julia, o que certamente não faz sentido em uma construção comunitária.

Eu posso estar procurando no lugar errado com automações personalizadas; eu estava apenas explorando o que é possível. Claro, como um fórum para entusiastas de linguagens de programação, usuários intrépidos já estão pensando em programar bots que poderiam usar a API do Discourse para fazer isso.

1 curtida

Não tenho certeza se automação é o que você procura aqui, pois algo que parece crítico é a experiência do “usuário final”. Com automação, isso só seria substituído depois do fato.

Pensando nesse tipo de problema, eu provavelmente recomendaria usar um plugin personalizado ou um componente de tema.
Um componente de tema poderia funcionar assim:

  1. O usuário digita: ^Rand
  2. Uma chamada HTTP é feita para um serviço de backend que você hospeda, que lista todas as opções com URLs
  3. O usuário seleciona a que deseja e pressiona Enter
  4. O Markdown é trocado por [Random.rand!](https://docs.julialang.org/en/v1/stdlib/Random/#Random.rand!)

Um plugin que altere o pipeline de Markdown poderia funcionar de forma semelhante ao onebox e apenas criar links automaticamente enquanto você digita, deixando a sintaxe original. Ex: ^Random.rand

Entendo sua observação sobre o linkify não ser ideal, a descoberta é difícil, além disso, você pode ter que hospedar um site para normalizá-lo para lookup.docs.julialang.org?q=Random.rand!

Certamente um problema muito interessante. Acho que a experiência do usuário de um componente de tema pode ser razoável aqui.

2 curtidas

Isso é muito legal, obrigado pelas ideias e dicas aqui! Vou levar isso para um tópico separado se e quando tiver mais perguntas (ou respostas :)).

2 curtidas

Eu acho que um plugin personalizado que é acionado na alteração de postagem é o que você quer criar. Suspeito que, para o seu caso de uso, que tem um gatilho simples, seria mais fácil escrever um plugin do que usar o plugin de automação.

1 curtida

@McUles e @nathank vocês encontraram as informações que procuravam?

Para que a automação personalizada funcione, tive que modificar os seguintes arquivos:

Criado o script de automação personalizado

Atualizado: server.en.yml

adicionado nome da automação personalizada; título; e descrição na seção de scriptables do arquivo yml.

Atualizado: client.en.yml

adicionado nome da automação personalizada em scriptables; adicione a palavra-chave ‘field’; dentro da palavra-chave field adicione ‘field_name’ seguido por ‘label’ e ‘description’

Atualizado: scripts.rb

adicione o nome da automação personalizada na lista de scripts. Exemplo: FILE_NAME = “file_name”

Atualizado: plugin.rb

dentro de ‘after_initialize do’, adicione o caminho para o script de automação personalizado. Exemplo: ‘lib/discourse_automation/scripts/file_name’

Não entendi completamente o que você colocou lá - são modificações no plugin Automations ou componentes importantes de um plugin irmão que contém a automação personalizada?

Seria ótimo ter isso consolidado em o OP

2 curtidas

É isso. Eu realmente não sabia a resposta para sua pergunta, então aqui está como encontrei uma resposta e também a resposta para “Existe algum exemplo, em algum lugar, que eu possa ver?”

Primeiro, obtenha isto: GitHub - discourse/all-the-plugins

Em seguida, use grep para algo como “add_automation_scriptable” e, em seguida, você poderá descobrir o que o está usando.

 (main) pfaffman@noreno:~/src/discourse-repos/all-the-plugins/official$ grep -r add_automation_scriptable
discourse-assign/plugin.rb:    add_automation_scriptable("random_assign") do
discourse-chat-integration/plugin.rb:    add_automation_scriptable("send_slack_message") do
discourse-chat-integration/plugin.rb:    add_automation_scriptable("send_chat_integration_message") do
discourse-data-explorer/plugin.rb:      add_automation_scriptable("recurring_data_explorer_result_pm") do
discourse-data-explorer/plugin.rb:      add_automation_scriptable("recurring_data_explorer_result_topic") do

Então, talvez dê uma olhada em discourse-assign ou data-explorer.

2 curtidas