API를 통해 이미지로 맞춤형 배지 만들기

네, 포함된 배지들은 … 꽤 좋습니다. 전혀 문제없는 것이죠. 하지만 더 많은 것을 원한다면요? 사전 정의된 심볼 세트를 넘어 서고 싶다면요? 물론 업로드할 수 있는 관리자 페이지가 있습니다. 하지만 배지를 아주 많이 만들고 싶다면요?

좋습니다! API를 통해 이를 수행할 수 있습니다. 어떻게 작동하는지 보여주는 Python 코드를 아래에 첨부합니다. (완벽함을 위해 마지막에 배지를 수여하는 부분도 포함되어 있습니다.)

Python 프로그래머가 아니더라도 읽기 꽤 쉬울 것입니다. 원하시면 curl을 사용하여 명령줄에서 이를 재현할 수도 있습니다.

이 코드는 API 키가 환경 변수 DISCOURSE_API_KEY에 설정되어 있음을 전제로 합니다.

#!/usr/bin/python3

import os         # 환경 변수 읽기용
import sys        # 종료용. :)
import hashlib    # 이미지 해시용

import requests   # 실제 작업을 수행


# 사이트 및 인증 관련 사항
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 must be set in the environment", file=sys.stderr)
    sys.exit(2)

# 배지 정보. 실제 사용 시에는 이를 하드코딩하지 않을 것입니다.
NAME = "Apex"
IMAGE = "apex.png"
DESCRIPTION = "Blessing of the FPL"
MORE = "You are awesome and everyone should know!"
TARGET_USER = "mattdm"

# 위에서 가져온 인증 헤더.
HEADERS = {'Api-Key': DISCOURSE_API_KEY, 'Api-Username': DISCOURSE_API_USER}

# 여기에서 배지 그룹의 ID와 설명, 그리고 다양한 배지 유형을 가져올 수 있습니다.
# 원한다면요. 이 예제에서는 사용하지 않습니다.
r = requests.get(f"https://{DISCOURSE}/admin/badges.json", headers=HEADERS)
if r.status_code != 200:
    print(f"Error getting badge list: got {r.status_code}", file=sys.stderr)
    sys.exit(1)

# 이름이 이미 존재하지 않는지 확인합니다.
# 더 나은 방법이 있을 수 있지만, 이것으로 충분합니다.
if NAME in list(map(lambda x: x['name'], r.json()['badges'])):
    print(f"Error: badge '{NAME}' already exists.")
    sys.exit(3)

# 이미지를 읽습니다. 실제 코드에서는 더 나은 오류 처리가 필요할 것입니다!
with open(IMAGE, "rb") as f:
    image_data = f.read()

# 이미지 파일 크기/차원에 대한 일관성 검사를 여기서 수행해야 할 것입니다....

# 하지만 어쨌든, 업로드할 정보를 패키지로 조립합니다.
# 여기서 정말 어려운 부분은 이미지 체크섬을 얻는 것입니다.
file_info = {'file_name': f'{IMAGE}',
             'file_size': f'{len(image_data)}',
             'type': 'badge_image',
             'metadata[sha1-checksum]': hashlib.sha1(image_data).hexdigest(),
             }


# 그리고 Discourse에 어디로 전송해야 하는지 요청합니다.
r = requests.post(
    f"https://{DISCOURSE}/uploads/generate-presigned-put", json=file_info, headers=HEADERS)
if r.status_code != 200:
    print(
        f"Error asking where to upload the image: got {r.status_code}", file=sys.stderr)
    sys.exit(1)

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

# 이제 알려준 위치에 배치합니다.
r = requests.put(upload_url, data=image_data)
if r.status_code != 200:
    print(
        f"Error uploading image to external storage: got {r.status_code}", file=sys.stderr)
    sys.exit(1)

# 그리고 Discourse에 성공했음을 알리고 나중에 참조할 수 있는 ID를 가져옵니다.
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 completing upload: got {r.status_code}", file=sys.stderr)
    sys.exit(1)
image_id = r.json()['id']


# 참고: font awesome을 사용하려면 `image_upload_id`를 비워두고
# 대신 `icon`을 해당하는 font awesome 이름으로 설정하십시오. 물론
# 그 경우 이미지 업로드 관련 사항을 모두 건너뛸 수 있습니다!
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 creating badge: got {r.status_code}", file=sys.stderr)
    sys.exit(1)

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

print(f'Badge "{NAME}: {DESCRIPTION}" has been created as {badge_id}!')


# 그리고 완전성을 위해 배지를 수여합니다!

# 게시물이나 주제에 대한 링크를 포함해야 하는 "reason"도 추가할 수 있습니다.
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 granting badge: got {r.status_code}", file=sys.stderr)
    sys.exit(1)

print(f'User {TARGET_USER} has been awarded the new badge!')

7개의 좋아요

불행히도 이를 위해 전역 관리자 API 키가 필요합니다. 팀이 업로드 권한, 배지 생성 및 편집, 배지 할당, 배지 제거에 대한 세분화된 검사를 수행할 수 있는 방법을 구현해 주기를 희망합니다.

이제 이를 위해 전역 관리자 API 키가 필요하지 않습니다. API 키를 생성할 때 배지 생성, 부여 또는 삭제와 관련된 다양한 구체적인 사항을 지정할 수 있습니다. 이 예제와 같이 자체 이미지를 추가하려면 "upload"를 잊지 마세요. 이는 배지 API 권한과 별도의 섹션에 있습니다.

2개의 좋아요

원래 게시글에서 실수로 sha 해시 다이제스트 함수를 문자열 안에 넣어버렸는데, 그래서 실제로 다이제스트가 아니라 해당 문자열이 그대로 Python 코드로 설정되었습니다. 그래도 동작은 했지만, 사실은 원래대로 제대로 설정해야 합니다. 어떤 부분이 이를 확인하는지는 모르겠지만, 만약 이 부분을 잘못 설정하면 언젠가 이미지가 손상된 것으로 표시될 가능성이 높습니다. 어쨌든, 위의 코드를 수정해서 이제 올바르게 설정해 두었습니다. 다른 버그를 발견하시면 알려주세요!

3개의 좋아요