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