Crie uma insígnia personalizada com uma imagem através da API

Claro, os emblemas incluídos são… bons. Não há nada de errado com eles. Mas, e se você quiser mais? E se você quiser ir além do conjunto de símbolos pré-definido? Claro, há uma página de administração onde você pode carregá-los. Mas e se você quiser criar um monte de emblemas?

Bem, boas notícias! Você pode fazer isso através da API. Aqui está um código Python mostrando como funciona. (E ele concede o emblema no final, para garantir.)

Isso deve ser fácil de ler, mesmo que você não seja um programador Python. Você pode até replicá-lo com curl na linha de comando, se quiser.

Isso espera que sua chave de API esteja em uma variável de ambiente DISCOURSE_API_KEY.

#!/usr/bin/python3

import os         # Para ler o ambiente
import sys        # Para sair. :)
import hashlib    # Para o hash da imagem

import requests   # Faz todo o trabalho real


# Informações do site e autenticação
DISCOURSE = "discussion.fedoraproject.org"
DISCOURSE_API_USER = "mattdm"
DISCOURSE_API_KEY = os.getenv("DISCOURSE_API_KEY")
if not DISCOURSE_API_KEY:
    print(f"Erro: DISCOURSE_API_KEY deve ser definido no ambiente", file=sys.stderr)
    sys.exit(2)

# Informações para o seu emblema. Presumivelmente, em uso real você não codificaria isso.
NAME = "Apex"
IMAGE = "apex.png"
DESCRIPTION = "Bênção da FPL"
MORE = "Você é incrível e todos deveriam saber!"
TARGET_USER = "mattdm"

# Nossos cabeçalhos de autenticação, de cima.
HEADERS = {'Api-Key': DISCOURSE_API_KEY, 'Api-Username': DISCOURSE_API_USER}

# A partir disso, você pode obter os IDs e descrições de grupos de emblemas, e
# os vários tipos de emblemas. Se você quiser. Não estou usando isso neste exemplo.
r = requests.get(f"https://{DISCOURSE}/admin/badges.json", headers=HEADERS)
if r.status_code != 200:
    print(f"Erro ao obter lista de emblemas: obteve {r.status_code}", file=sys.stderr)
    sys.exit(1)

# Verifica se o nome já existe.
# Provavelmente há uma maneira melhor de fazer isso, mas será suficiente.
if NAME in list(map(lambda x: x['name'], r.json()['badges'])):
    print(f"Erro: o emblema '{NAME}' já existe.")
    sys.exit(3)

# Lê a imagem. Você gostaria de um melhor tratamento de erros aqui em código real!
with open(IMAGE, "rb") as f:
    image_data = f.read()

# Provavelmente deveria fazer alguma verificação de sanidade no
# tamanho/dimensões do arquivo de imagem bem aqui....

# Mas de qualquer forma, monta um pacote de informações sobre ele para fazer upload.
# A única coisa complicada aqui realmente é obter o checksum da imagem.
file_info = {'file_name': f'{IMAGE}',
             'file_size': f'{len(image_data)}',
             'type': 'badge_image',
             'metadata[sha1-checksum]': hashlib.sha1(image_data).hexdigest(),
             }


# E pergunta ao Discourse para onde enviar.
r = requests.post(
    f"https://{DISCOURSE}/uploads/generate-presigned-put", json=file_info, headers=HEADERS)
if r.status_code != 200:
    print(
        f"Erro ao perguntar onde fazer upload da imagem: obteve {r.status_code}", file=sys.stderr)
    sys.exit(1)

upload_url = r.json()['url']
upload_uid = r.json()['unique_identifier']

# Agora coloque onde nos foi dito para colocar.
r = requests.put(upload_url, data=image_data)
if r.status_code != 200:
    print(
        f"Erro ao fazer upload da imagem para armazenamento externo: obteve {r.status_code}", file=sys.stderr)
    sys.exit(1)

# E diga ao Discourse que funcionou, e receba um ID que podemos referenciar mais tarde.
r = requests.post(f"https://{DISCOURSE}/uploads/complete-external-upload",
                  data=f'unique_identifier={upload_uid}', headers=HEADERS)
if r.status_code != 200:
    print(f"Erro ao completar upload: obteve {r.status_code}", file=sys.stderr)
    sys.exit(1)
image_id = r.json()['id']


# Nota: se você quiser usar font awesome, deixe `image_upload_id` em branco e
# em vez disso, defina `icon` para o nome correspondente do font awesome. E claro
# você pode pular toda a parte de upload de imagem nesse caso!
badge = {'allow_title': 'true',
         'multiple_grant': 'false',
         'listable': 'true',
         'show_posts': 'false',
         'target_posts': 'false',
         'name': f'{NAME}',
         'description': f'{DESCRIPTION}',
         'long_description': f'{DESCRIPTION} {MORE}',
         'image_upload_id': f'{image_id}',
         'badge_grouping_id': '5',
         'badge_type_id': '1',
         }


r = requests.post(
    f"https://{DISCOURSE}/admin/badges.json", json=badge, headers=HEADERS)
if r.status_code != 200:
    print(f"Erro ao criar emblema: obteve {r.status_code}", file=sys.stderr)
    sys.exit(1)

badge_id = r.json()['badge']['id']

print(f'Emblema "{NAME}: {DESCRIPTION}" foi criado como {badge_id}!')


# E para completar, conceda o emblema!

# Você também pode adicionar um "motivo", que deve vincular a um post ou tópico.
bestow = {'username': f"{TARGET_USER}", 'badge_id': f'{badge_id}'}

r = requests.post(f"https://{DISCOURSE}/user_badges",
                  json=bestow, headers=HEADERS)
if r.status_code != 200:
    print(f"Erro ao conceder emblema: obteve {r.status_code}", file=sys.stderr)
    sys.exit(1)

print(f'Usuário {TARGET_USER} recebeu o novo emblema!')

7 curtidas

Infelizmente, você precisa de uma chave de API de administrador global para isso. Espero que a equipe implemente uma maneira de fazer verificações granulares para direitos de upload, criação e edição de distintivos, atribuição e remoção de distintivos.

Você não precisa mais de uma chave de API de administrador global para isso — ao criar uma chave de API, você pode especificar diferentes coisas específicas relacionadas à criação, concessão ou destruição de distintivos. Não se esqueça de “upload” se você quiser fazer o que este exemplo mostra e adicionar suas próprias imagens. Isso está em uma seção separada das permissões da API de distintivos.

2 curtidas

Na postagem original disso, eu acidentalmente coloquei a função de hash sha dentro de uma string, então isso foi definido literalmente como o código python, não o hash. Isso pareceu funcionar, mas provavelmente deveria estar certo em vez disso. Não tenho certeza do que está verificando isso, mas provavelmente se você fizer isso errado a imagem será marcada como corrompida em algum momento no futuro. De qualquer forma, editei o código acima para que esteja correto. Se você encontrar algum outro bug, por favor me avise!

3 curtidas