pythonからMastodon APIを使ってフォロワー・フォローを取得する方法

# はじめに
マストドンの管理ツール「[MASTMAN（マストマン）](https://mastman.net/)」を開発してるときにミスったAPIの使い方を紹介します。

# Docs
APIの使い方は、以下のように割とシンプルな感じ（のはずだった...）
> ```
> GET /api/v1/accounts/:id/followers
> ```
> Query parameters:
> max_id : Get a list of followers with ID less than this value
> since_id : Get a list of followers with ID greater than this value
> limit : Maximum number of followers to get (Default 40, Max 80)

参考：[Getting an account's followers](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#accounts)


# 僕のフォロワー数
![da7e85c5357d395b78c6a383b770be3b.png](https://qiita-image-store.s3.amazonaws.com/0/171192/3ffdab6c-0c81-54df-cb60-e64b98878949.png)

41件です！


# ダメなフォロワーの取得

以下のコードはフォロワーIDの最初の10件を取得して、その中から一番小さいIDを取得して次にそのIDよりも小さい10件をとっての繰り返しを行っている。

```py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib2
import urllib
import json
import re

access_token = 'xxxxx'

def get_followers(user_id, max_id=None):
    # デフォルトのlimitは40だけど、失敗例がわかりやすいので今回は10で！
    limit_num = 10
    url = "https://mstdn.jp/api/v1/accounts/%d/followers" %(user_id)

    param = []
    param.append(('limit', limit_num))
    if max_id:
        param.append(('max_id', max_id))
    url += "?" + urllib.urlencode( param )

    request = urllib2.Request(url)
    auth_header = 'Bearer %s' %(access_token)
    request.add_header('Authorization', auth_header)
    res = urllib2.urlopen(request)
    res_list = json.loads(res.read())

    # 結果がlimit_num(今回は10件）だったら次の10件を取得...
    if len(res_list) == limit_num:
        next_max_id = min(map(lambda x: x.get('id'), res_list))
        res_list.extend(get_followers(user_id, max_id=next_max_id))
    return res_list

def main():
    # toitechのID
    my_user_id = 96368
    res = get_followers(my_user_id)
    # 結果のアイテム数
    print len(res)
    # 結果のユニークなID数
    print len(set(map(lambda x: x.get('id'), res)))


if __name__ == '__main__':
    main()

```


これを実行すると、、、なんと10件しかとれない！！！

```bash
$ python get_follower.py
10
10
```


# 正しいフォロワーの取得

もう一度、Docsをちゃんと見てみると以下のような注意書きが...
> Note: max_id and since_id for next and previous pages are provided in the Link header. It is not possible to use the id of the returned objects to construct your own URLs, because the results are sorted by an internal key.

帰ってきた結果のIDをsince_idとかmax_idとかに指定するのは違うらしい。
headerを見ろと。

```py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib2
import urllib
import json
import re

access_token = 'xxxxx'

def get_followers(user_id, max_id=None):
    # デフォルトのlimitは40だけど、失敗例がわかりやすいので今回は10で！
    limit_num = 10
    url = "https://mstdn.jp/api/v1/accounts/%d/followers" %(user_id)

    param = []
    param.append(('limit', limit_num))
    if max_id:
        param.append(('max_id', max_id))
    url += "?" + urllib.urlencode( param )

    request = urllib2.Request(url)
    auth_header = 'Bearer %s' %(access_token)
    request.add_header('Authorization', auth_header)
    res = urllib2.urlopen(request)
    res_list = json.loads(res.read())

    # 結果がlimit_num(今回は10件）だったら次の10件を取得...
    if len(res_list) == limit_num:
        link_str = res.info().getheaders("link")[0]
        next_max_id = re.search("max_id=(\d+)>", link_str).group(1)
        res_list.extend(get_followers(user_id, max_id=next_max_id))
    return res_list


def main():
    # toitechのID
    my_user_id = 96368
    res = get_followers(my_user_id)
    # 結果のアイテム数
    print len(res)
    # 結果のユニークなID数
    print len(set(map(lambda x: x.get('id'), res)))


if __name__ == '__main__':
    main()
```


これを実行すると...ちゃんと41件になっている！めでたしめでたし。

```bash
$ python get_follower.py
41
41
```

# まとめ

忙しくてもドキュメントはちゃんと読もう

# いいわけ

以下のようなstatus（トゥート）の取得は、前者（ダメなフォロワーの取得）と同じやり方でもいける！トゥートは新しいものほどトゥートIDが大きいが、フォロワーは最近フォローされた人ほどユーザーIDが大きいとは限らないからそうなってるんでしょうね。

```

GET /api/v1/accounts/:id/statuses

```


# 宣伝
マストドンの管理ツール「[MASTMAN（マストマン）](https://mastman.net/)」絶賛開発中、みんなも使ってみてね！