Python > 멘션된 경우 게시물 내용 가져오기

Python으로 실험을 해보고 있습니다. 구체적으로 말씀드리면:

이 API 문서를 참고하고 있습니다:

Discourse API Docs / Discourse API Docs

Python 초보(학습 중)이며, @UserA가 어딘가에서 언급되면 해당 게시물을 단순히 받아들이는 방법을 시도하고 있습니다. 알림(notifications)에서 post_number를 가져온 후, 해당 게시물을 조회하여 게시물 내용을 검색하고 저장해야 한다고 생각했습니다.

그 후 게시물 내용을 데이터 변수로 저장하고, 이를 기반으로 몇 가지 실험을 해보려 합니다.

제 코드의 일부는 다음과 같습니다:

# lookup mentions
mention = client.notifications.json.get()

# print the posts
for post_number in mention:
    print(post_number)

이 코드의 현재 출력 결과는 다음과 같습니다:

notifications
total_rows_notifications
seen_notification_id
load_more_notifications

어리석은 질문일 수 있지만, API도 Python도 비교적 생소합니다. 하루 종일 이것으로 작업했지만 성공하지 못했는데, 올바른 방향을 알려주실 분이 있다면 매우 감사하겠습니다.

fluent-discourse 링크를 올려주셔서 감사합니다!

현재 Python에 익숙해지는 중이라, 제가 여기서 설명하는 것보다 더 나은 접근 방식이 있을 수 있습니다.

mention = client.notifications.json.get()

이것은 client를 생성할 때 설정한 username에 대한 언급(mention) 목록을 사전(dictionary)으로 반환합니다. client를 생성할 때 username을 UserA로 설정했을 것으로 추정합니다.

사전이 반환되는지 확인하려면 다음을 실행할 수 있습니다:

print(type(mention))

그 결과는 <class 'dict'>가 되어야 합니다.

사전의 키를 확인하려면 다음을 실행할 수 있습니다:

print(list(mention))

이것은 ['notifications', 'total_rows_notifications', 'seen_notification_id', 'load_more_notifications']를 반환합니다.

사용자의 경우, notifications 키 아래에 있는 값이 필요합니다:

notifications = mention['notifications']

이때 type(notifications)을 실행하면 이것이 목록(list)임을 알 수 있습니다. UserA의 언급으로 인해 트리거된 알림을 가져오려면, notification_type이 1로 설정된 항목을 찾기 위해 목록을 반복(iterate)해야 합니다. Discourse 알림 유형의 값을 확인하는 가장 쉬운 방법은 Discourse 사이트의 rails 콘솔에서 Notification.types를 실행하는 것입니다:

 pry(main)> Notification.types
=> {:mentioned=>1,
 :replied=>2,
 :quoted=>3,
 :edited=>4,
 :liked=>5,
 ...

따라서 client에 설정한 사용자名的(username)을 가진 사용자의 “mention” 알림을 가져오려면 다음과 같은 것을 시도해 보세요(작성하면서 Python 인터프리터에서 테스트 중입니다):

data = client.notifications.json.get()
notifications = data['notifications']
# "mention" 알림을 저장할 빈 목록 객체 생성
mentions = []
# `notification_type`이 `1`로 설정된 모든 항목을 찾기 위해 `notifications` 목록 반복
for notification in notifications:
    if notification['notification_type'] == 1:
        mentions.append(notification)

이것은 언급으로 인해 트리거된 알림의 목록을 생성합니다. 목록의 각 항목은 사전이 될 것입니다. 다음과 같은 것으로 확인할 수 있습니다:

for mention in mentions:
    print(mention['topic_id'], mention['post_number'])    

정말 유용하네요. 정보 주셔서 감사합니다. Python과 Discourse에 대한 정보가 밖에 많이 없어서 배우게 되어 좋고, 이 글이 다른 분들에게도 도움이 되었으면 합니다. 말씀하신 코드를 바로 시도해 봤는데, 예상대로 작동하는 것 같습니다. 물론 저는 멈추지 않고 계속 실험을 계속해서 5단계까지 도달하는 우회 방법을 찾았고, 게시글 원본(raw) 콘텐츠를 그대로 붙여넣을 수 있는 필요한 값을 가져오는 다른 방법도 찾았습니다.

제가 기본적으로 원하는 것은 다음과 같습니다.

  1. 알림 가져오기
  2. post_numbers 가져오기
  3. 게시글 콘텐츠 가져오기
  4. OpenAI로 답변 생성
  5. 답변 게시
  6. 해당 알림을 읽음으로 표시?
# 봇을 무한대로 실행하기 위한 루프 설정
while True:
    # user_actions 가져오기 및 게시글 ID 조회    
    data = {
    'username': 'theuser',
    # 멘션과 답변의 경우 필터 6 + 7을 가져와야 하는데, 어째서인지 JSON 응답이 하나만 반환됩니다.
    'filter': 6 | 7,
    'limit': 10,
    }
    
    unread_mentions = client.user_actions.json.get(data)
    print(unread_mentions)

    user_actions = unread_mentions['user_actions']

    # 각 읽지 않은 멘션에 대해 반복
    for user_action in unread_mentions['user_actions']:
        topic_id = user_action['topic_id']
        post_number = user_action['post_number']
        post_id = user_action['post_id']
        username = user_action['username']

        print(f'Topic ID: {topic_id}, Post Number: {post_number}, Post ID: {post_id}, Reply to: {username}')
    
        # 게시글 콘텐츠 가져오기   
        post = client.posts[post_id].json.get()

        # 게시글 콘텐츠 출력
        post_content = post['raw']

        # 게시글에 대한 응답 생성
        response = generate_response(post_content)
    
        # 포럼에 응답 게시
        content = {
            'topic_id': topic_id,
            'raw': response + ' @' + str(username)
        }
        make_post = client.posts.json.post(content)

현재 두 가지 문제가 있습니다:

  • 멘션이 하나만 가져와집니다. 무언가를 빠뜨린 것 같은데, 아직 원인을 찾지 못했습니다.
  • 물론 게시글에는 한 번만 답변해야 하며, 루프를 돌리고 있으므로 이를 방지하는 장치가 필요합니다.
    • post_id를 .txt 파일에 저장할 수 있습니다 (얼마나 많은 데이터를 저장할 수 있을지 확실하지 않지만, 꽤 많은 멘션을 처리할 수 있을 것 같습니다.)
    • 제 이상적인 해결책은 알림을 읽음으로 표시하는 것이었습니다. 하지만 우리의 코드 어디에서도 읽지 않은 멘션을 가져오는 것이 아니라는 것을 깨달았습니다 (처음에는 읽지 않은 알림을 가져오려고 시도했지만, /posts/get.json을 사용해 “raw” 콘텐츠를 가져오기 위해 필요한 post_id 변수를 추출/저장하는 데 실패했습니다.)

최종 목표는 OpenAI API로 응답을 피드하는 것입니다. 하지만 먼저 위의 문제를 해결하는 방법을 찾아야 합니다.

원하시는 것이 특정 사용자 이름(예: UserA)이 게시물에서 언급될 때 OpenAI로부터 응답을 생성하는 것이라면, Discourse로 알림을 가져오기 위해 API 요청을 보내는 대신 Discourse 알림 이벤트 웹훅을 Python 애플리케이션 서버로 설정할 수 있습니다. Python 애플리케이션은 웹훅 페이로드를 확인해야 합니다. 페이로드의 notification_type이 1로 설정되어 있고, user_id가 알림을 받기를 원하는 사용자의 ID와 일치하는 경우, 페이로드의 original_post_id 필드에 설정된 게시물을 가져오기 위해 Discourse로 API 요청을 보내야 합니다. 그런 다음 해당 요청에서 반환된 게시물 내용을 OpenAI로 전송합니다.

언급 알림의 페이로드가 어떤 모습인지 보여주는 예시는 다음과 같습니다:

{
  "notification": {
    "id": 55267,
    "user_id": 2,
    "notification_type": 1,
    "read": false,
    "high_priority": false,
    "created_at": "2022-12-08T23:23:36.379Z",
    "post_number": 5,
    "topic_id": 277,
    "fancy_title": "I should be able to call this topic anything",
    "slug": "i-should-be-able-to-call-this-topic-anything",
    "data": {
      "topic_title": "I should be able to call this topic anything",
      "original_post_id": 10999,
      "original_post_type": 1,
      "original_username": "scossar",
      "revision_number": null,
      "display_username": "scossar"
    }
  }
}

이 방식의 장점은 Discourse 사이트로 대량의 API 요청을 보내지 않고도 실시간으로 알림을 받을 수 있다는 것입니다.

웹훅 방식을 선택한다면, Discourse에서 웹훅의 “Secret” 필드를 설정한 후 Python 애플리케이션에서 요청을 검증하여 시크릿 키가 올바르게 설정되었는지 확인하는 것이 좋습니다. 저는 아직 Python 애플리케이션에서 이를 수행하기 위한 라우트를 설정해 보지 않았습니다. WordPress에서 웹훅의 Secret을 검증하는 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다: wp-discourse-custom-webhook-request/wp-discourse-custom-webhook-request.php at master · scossar/wp-discourse-custom-webhook-request · GitHub.

답장 주셔서 감사합니다. 정말 감사드립니다.

Discourse 웹훅에 대해 살펴본 적이 없었습니다. 이번 기회에 자세히 알아볼 예정입니다.

수정: 제가 오해한 것 같습니다. 제가 찾는 것은 1번과 2번 유형인 것 같습니다. 2번 유형은 사용자에게 답장이 있을 경우 트리거됩니다. 이로 인해 이 게시물은 사실상 무의미해집니다. 이것을 시험해 보다가 다음과 같은 질문이 떠올랐습니다:

mentioned는 명확합니다. 명시적인 @Mention이 있을 경우 type == 1입니다.

하지만 replied의 경우 조금 혼란스럽습니다.

일반 버튼 중 하나를 통해 토픽에 답장하면 답장으로 입력됩니다. 이는 당연합니다.

하지만 특정 사용자의 게시물에 대한 답장 버튼을 클릭하면 언급(mention)일 것이라고 생각하지만, 여전히 2번 유형 알림입니다.

This should be a mention type notification

“사용자에게 답장” 알림은 1번 유형이 되어야 하지 않나요? 이는 게시물에 @Mention이 없을 뿐, 언급과 기본적으로 정확히 동일한 것이기 때문입니다.

참고로, meta 어디에도 관련 정보가 없어 알림 유형을 여기에 게시합니다. 이러한 유형의 알림에 새로운 유형을 부여하거나, 단순히 1번 유형으로 변경할 수 있을 것입니다.

[1] pry(main)> Notification.types
=> {:mentioned=>1,
 :replied=>2,
 :quoted=>3,
 :edited=>4,
 :liked=>5,
 :private_message=>6,
 :invited_to_private_message=>7,
 :invitee_accepted=>8,
 :posted=>9,
 :moved_post=>10,
 :linked=>11,
 :granted_badge=>12,
 :invited_to_topic=>13,
 :custom=>14,
 :group_mentioned=>15,
 :group_message_summary=>16,
 :watching_first_post=>17,
 :topic_reminder=>18,
 :liked_consolidated=>19,
 :post_approved=>20,
 :code_review_commit_approved=>21,
 :membership_request_accepted=>22,
 :membership_request_consolidated=>23,
 :bookmark_reminder=>24,
 :reaction=>25,
 :votes_released=>26,
 :event_reminder=>27,
 :event_invitation=>28,
 :chat_mention=>29,
 :chat_message=>30,
 :chat_invitation=>31,
 :chat_group_mention=>32,
 :chat_quoted=>33,
 :assigned=>34,
 :question_answer_user_commented=>35,
 :watching_category_or_tag=>36}

그렇지 않다고 생각합니다. 멘션 알림("notification_type": 1)은 게시물에 @username이 추가되는 특정 상황을 위해 설계되었습니다. 직접 답장에 동일한 알림 유형을 사용하면 혼란이 생길 수 있습니다.

주의해야 할 경계 사례가 있습니다. 사용자의 주제(topic)에 대한 답장에 사용자 이름이 추가되면, 해당 사용자에게 전송되는 알림은 "notification_type": 2가 아닌 "notification_type": 1이 됩니다. 예를 들어, 여기에서 사용자 이름을 멘션하면 이 게시물은 제가 사용자에게 답장했다는 알림이 아니라, 사용자를 멘션했다는 알림을 트리거해야 합니다: @MarcP.

본인이 아닌 다른 사용자가 이 주제를 보고 있었다면, 유형이 9(게시됨)로 설정된 알림을 받게 됩니다.