마스토돈 관리자(sysop)의 주요 일거리 중 하나는 마스토돈의 새 릴리즈가 나오면 업데이트를 하는 것입니다.

마스토돈엔 이미 새 마스토돈 버전이 나오면 알려주는 봇이 있기는 합니다. 다만 이 봇은 직접 관리자들에게 멘션을 해 주는 게 아니라서 지속적으로 눈여겨 보지 않으면 의미가 없습니다.

그래서 저는 개인적으로 마스토돈 릴리즈 페이지의 Atom 피드를 받아 Pushbullet으로 알림을 받는 식으로 마스토돈의 새 릴리즈 알림을 받아 보는 편이었습니다. 하지만 다른 관리자들도 새 릴리즈 알림을 받아 보고 싶어 하는 것 같아 릴리즈 알림 봇을 새로 만들게 되었습니다.


동작 구상

기본적으로 다음과 같은 동작을 하는 걸 생각했습니다.

  • 마스토돈의 새 릴리즈가 있는지 지속적인 감시
  • 각 서버들의 버전을 지속적으로 감시
    • 업데이트를 하지 않은 경우 주기적으로 새로 알림

마스토돈의 릴리즈 버전을 확인하는 일과 서버의 버전을 확인하는 것은 쉽지만 가장 마지막의 주기적인 알림이 가장 어렵겠습니다. 새 버전 확인을 5분마다 한다고 업그레이드를 하지 않은 서버 관리자에게 5분마다 알림을 보낸다면 그건 스팸일테니까요. 그래서 추천을 받았습니다. 1일, 2일, 4일, 8일, … 이렇게 2의 제곱수마다 알리는 겁니다.

실제 구현

마스토돈의 새 릴리즈를 받아 오는 건 쉽습니다. Github은 Atom 피드를 제공 하고 있거든요.
https://github.com/tootsuite/mastodon/releases.atom 이 피드를 이용해서 가장 최신 엔트리의 타이틀을 받아 오면 됩니다.

feed = feedparser.parse('https://github.com/tootsuite/mastodon/releases.atom')
latest_release = feed.entries[0]

current_version = latest_release.title
current_updated = latest_release.updated

각 서버의 버전을 가져 오는 것도 아주 쉽습니다. API가 이미 있거든요.\

curl https://${domain}/api/v1/instance | jq .version

그렇다면 최대 문제인 주기적인 알림은 어떻게 구현할까요?
마스토돈 릴리즈 피드는 릴리즈 날짜까지 나옵니다. 이걸 이용해 봅시다.

일단 마지막으로 관리자에게 알림을 준 날짜는 따로 저장해야 합니다. 이걸 last_notified라고 합니다. 그리고 새 버전의 릴리즈 날짜를 release_date, 오늘 날짜를 today라고 가정합니다.

마지막 릴리즈로부터 지난 날은 A = today - release_date입니다. 마지막 릴리즈로부터 마지막 알림까지 지난 날은 B = last_notified - release_date겠죠. 이걸 가지고 어떻게 하면 두 배가 될 때마다 알림을 줄 수 있는 지 수학적인 장난을 쳐 봅시다.

간단히 말하면 AB의 두 배가 될 때 알림을 날리면 됩니다. 이걸 코드로 표현하면 다음과 같아집니다.

def should_notify(
        last_notified: datetime.datetime,
        release_date: datetime.datetime):
    today = datetime.datetime.now(datetime.timezone.utc)
    days_notified = (last_notified - release_date).days
    days_passed = (today - release_date).days

    notified_level = math.log(days_notified, 2) if days_notified else -1
    passed_level = math.log(days_notified, 2) if days_passed else -1

    return passed_level - notified_level >= 1

로그를 씌운 다음에 1 이상 차이가 나면 두 배 이상 지난 것이니 알림을 뿌리면 되겠죠. 하지만 로그는 인자로 0을 받을 수 없으므로 0일 때는 -1을 결과로 가지도록 합니다(1을 넣으면 0이 나오기 때문)

이렇게 1, 2, 4, 8, 16, …일마다 알림을 줄 수 있게 되었습니다.

또 다른 문제

마스토돈 API

마스토돈의 API를 써 보신 적이 있나요? 마스토돈의 API는 각 계정에 대해 이것저것 정보를 주지만 정작 우리가 필요한 정보는 주지 않아서 다른 API와 함께 사용해 해결을 봐야 할 경우가 있습니다.

대표적인 경우가 계정이 속한 도메인을 구하는 것입니다.Account 엔티티는 따로 도메인 정보를 가지고 있지 않습니다. 그렇기 때문에 우리가 알아서 알고 있는 정보들을 조합해 구해내야 하죠.
acct 속성은 jarm@qdon.space와 같은 형식을 따르기 때문에 @으로 문자열을 나누고 뒷부분을 가져오면 간단하게 도메인을 구할 수 있는 것 같아 보입니다. 하지만 acct는 Full acct가 아닙니다. 로컬 유저에 대해서는 jarm@qdon.space가 아니라 그냥 jarm이라고 주게 됩니다. 따라서 우리는 acct를 잘 보고 도메인 정보가 뒤에 붙어 있지 않으면 우리가 접속 한 서버의 도메인 정보를 붙여야 합니다.

우리가 접속 한 서버의 정보는 /api/v1/instance 엔드포인트에 나와 있습니다. 해당 엔드포인트에 요청을 보내고 uri 속성을 읽으면 됩니다.

curl https://${domain}/api/v1/instance | jq .uri

이를 이용해 계정의 Full acct를 구하는 함수도 다음과 같이 만들 수 있습니다.

# Uses Mastodon.py library

def full_acct(account):
    if '@' in account.acct:
        return account.acct

    domain = api.instance().uri
    return f'{account.acct}@{domain}'

botsin.space의 정책

이 봇은 봇 전용 서버라 할 수 있는 botsin.space에 살게 되었습니다만 문제가 있었습니다. 계속 API 에러가 나서 뭘 잘못 사용했나 1시간이 넘게 고민을 했는데 결과적으로 botsin.space는 자체적인 커스텀을 적용해서 나를 팔로우 하지 않는 유저에게는 멘션을 보낼 수 없도록 되어 있었습니다. API 에러코드엔 그걸 알 수 있는 정보가 없고 그냥 “엔티티 문제, 게시물 작성 불가"라고만 되어 있어서 해맸습니다. 별개로 커스텀이 들어간 부분의 소스코드를 보고 싶은데 소스코드에 대한 링크는 안 보이는군요. AGPL이기 때문에 수정사항에 대한 소스코드는 반드시 공개해야 합니다만..

완성

간략한 설명은 여기까지입니다. 자세한 구현사항을 보고 싶으시다면 소스코드를 보시면 됩니다. 사용하고 싶으시다면 update_bird@botsin.space를 팔로우 하신 후 멘션(DM도 됩니다)으로 “register"가 포함 된 메시지를 보내면 당신을 관리자로 보고 새 업데이트가 있을 때마다, 업데이트를 안 하면 지속적으로 멘션을 보내 알려 줍니다.
구독해지는 “unregister"가 포함 된 메시지를 보내면 됩니다.

아직까지는 관리자가 아니더라도 등록만 하면 무조건 관리자로 취급하며 새 버전 알림과 함께 업데이트를 하지 않았을 때의 알림도 모두 받게 되지만 이를 구분하여 새 버전 알림만 받도록 할 수 있는 기능을 넣을까 합니다.