Crear una insignia personalizada con una imagen a través de la API

Claro, las insignias incluidas son… agradables. No hay nada de malo en ellas. Pero, ¿y si quieres más? ¿Qué pasa si quieres ir más allá del conjunto de símbolos predefinido? Claro, hay una página de administración donde puedes subirlas. Pero, ¿y si quieres crear un montón de insignias?

¡Buenas noticias! Puedes hacerlo a través de la API. Aquí tienes código Python que muestra cómo funciona. (Y otorga la insignia al final, para mayor seguridad).

Esto debería ser bastante fácil de leer, incluso si no eres un programador de Python. Incluso puedes replicarlo con curl en la línea de comandos, si lo deseas.

Esto espera que tu clave API esté en una variable de entorno DISCOURSE_API_KEY.

#!/usr/bin/python3

import os         # Para leer el entorno
import sys        # Para salir. :)
import hashlib    # Para el hash de la imagen

import requests   # Hace todo el trabajo real


# Sitio y datos de autenticación
DISCOURSE = "discussion.fedoraproject.org"
DISCOURSE_API_USER = "mattdm"
DISCOURSE_API_KEY = os.getenv("DISCOURSE_API_KEY")
if not DISCOURSE_API_KEY:
    print(f"Error: DISCOURSE_API_KEY debe estar configurado en el entorno", file=sys.stderr)
    sys.exit(2)

# Información para tu insignia. Presumiblemente, en uso real no codificarías esto.
NAME = "Apex"
IMAGE = "apex.png"
DESCRIPTION = "Bendición de la FPL"
MORE = "¡Eres increíble y todo el mundo debería saberlo!"
TARGET_USER = "mattdm"

# Nuestras cabeceras de autenticación, de arriba.
HEADERS = {'Api-Key': DISCOURSE_API_KEY, 'Api-Username': DISCOURSE_API_USER}

# A partir de esto, puedes obtener los IDs y descripciones de los grupos de insignias, y
# los diversos tipos de insignias. Si quieres. No se usa en este ejemplo.
r = requests.get(f"https://{DISCOURSE}/admin/badges.json", headers=HEADERS)
if r.status_code != 200:
    print(f"Error al obtener la lista de insignias: se obtuvo {r.status_code}", file=sys.stderr)
    sys.exit(1)

# Comprueba que el nombre no exista ya.
# Probablemente haya una mejor manera de hacer esto, pero será suficiente.
if NAME in list(map(lambda x: x['name'], r.json()['badges'])):
    print(f"Error: la insignia '{NAME}' ya existe.")
    sys.exit(3)

# Lee la imagen. ¡Querrías un mejor manejo de errores aquí en código real!
with open(IMAGE, "rb") as f:
    image_data = f.read()

# Probablemente deberías hacer algunas comprobaciones de cordura sobre el
# tamaño/dimensiones del archivo de imagen justo aquí....

# Pero de todos modos, ensambla un paquete de información sobre él para subirlo.
# Lo único complicado aquí es realmente obtener la suma de verificación de la imagen.
file_info = {'file_name': f'{IMAGE}',
             'file_size': f'{len(image_data)}',
             'type': 'badge_image',
             'metadata[sha1-checksum]': hashlib.sha1(image_data).hexdigest(),
             }


# Y pregunta a Discourse a dónde enviarlo.
r = requests.post(
    f"https://{DISCOURSE}/uploads/generate-presigned-put", json=file_info, headers=HEADERS)
if r.status_code != 200:
    print(
        f"Error al preguntar dónde subir la imagen: se obtuvo {r.status_code}", file=sys.stderr)
    sys.exit(1)

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

# Ahora ponlo donde nos dijeron que lo hiciéramos.
r = requests.put(upload_url, data=image_data)
if r.status_code != 200:
    print(
        f"Error al subir la imagen al almacenamiento externo: se obtuvo {r.status_code}", file=sys.stderr)
    sys.exit(1)

# Y dile a Discourse que funcionó, y obtén un ID al que podamos referenciar más 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"Error al completar la carga: se obtuvo {r.status_code}", file=sys.stderr)
    sys.exit(1)
image_id = r.json()['id']


# Nota: si quieres usar Font Awesome, deja `image_upload_id` en blanco y
# en su lugar establece `icon` al nombre correspondiente de Font Awesome. Y por supuesto
# ¡puedes omitir toda la parte de carga de imágenes en ese 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"Error al crear la insignia: se obtuvo {r.status_code}", file=sys.stderr)
    sys.exit(1)

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

print(f'¡La insignia "{NAME}: {DESCRIPTION}" ha sido creada como {badge_id}!')


# Y para completar, ¡otorga la insignia!

# También puedes añadir una "razón", que debe enlazar a una publicación o tema.
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"Error al otorgar la insignia: se obtuvo {r.status_code}", file=sys.stderr)
    sys.exit(1)

print(f'¡El usuario {TARGET_USER} ha recibido la nueva insignia!')

7 Me gusta

Desafortunadamente, necesitas una clave de API de administrador global para esto. Espero que el equipo implemente una forma de realizar comprobaciones granulares para los derechos de carga, creación y edición de insignias, asignación y eliminación de insignias.

Ya no necesitas una clave de API de administrador global para esto: al crear una clave de API, puedes especificar diferentes cosas específicas relacionadas con la creación, concesión o destrucción de insignias. No olvides “upload” si quieres hacer lo que muestra este ejemplo y añadir tus propias imágenes. Eso está en una sección separada de los permisos de la API de insignias.

2 Me gusta

En la publicación original de esto, accidentalmente puse la función hash digest sha dentro de una cadena, por lo que se estableció literalmente como el código de python, no el digest. Eso pareció funcionar, pero probablemente debería ser correcto en lugar de eso. No estoy seguro de qué está comprobando eso, pero probablemente si haces esto mal, la imagen se marcará como corrupta en algún momento en el futuro. De todos modos, he editado el código anterior para que sea correcto. Si detectas algún otro error, ¡por favor házmelo saber!

3 Me gusta