Python逆引きサンプルコード50選(Mastodon API初級編)

この記事は約 48 分で読めます。

本記事は、こちらの解答編を兼ねたサンプルスクリプト集となります。

Mastodonで始めるPythonプログラミング!腕試しテスト50本ノック(初級編)
どうも、匠です。匠は、2017年にMastodonで遊びたくて、苦手なプログラミングを克服して、Pythonを習得しました。この経験から Mastodon の API を使って練習するのは、下記の理由でプログラミング学習に有効だ...

皆様のPython力向上&マストドンライフの一助になれば幸いです。

スポンサーリンク

前提

今回の解答は、下記の前提で記載しています。

  • なるべく requestsモジュール もしくは Mastodon.py を利用して、極力シンプルかつ読みやすいコードにする
    • Mastodon.py を優先的に使用する
  • Python初級者を対象とする
    • プログラミングの基礎知識はある
    • Pythonの基礎的な文法は分かる、あるいはリファレンスなどを読めば分かる
    • 実用的なプログラムはあまり作ったことがない
  • HTTPリクエストやAPIなど、ITに関する基礎知識が分かる
  • Mastodon APIおよび requestsモジュールMastodon.pyモジュールのリファレンスを読みながら利用できる。

もし requestsモジュールでの作り方を知りたい方がいらっしゃれば、コチラまでご要望ください。

このエントリーをはてなブックマークに追加
Twitter: @itsumonotakumi
Mastodon: @[email protected]

スポンサーリンク

サンプルスクリプト

こちらからダウンロードできるようにしました。

itsumonotakumi/mastodon-python-exercise-50
Sample scripts of Python 50 exercise for Mastodon: - itsumonotakumi/mastodon-python-exercise-50
スポンサーリンク

解答1. APIの操作準備

1. インスタンスへ疎通確認してください。
(ヒント:HTTPSポートへ何らか通信できればOK)

HOSTNAMEはmastodonインスタンスおよびUSERNAMEはユーザーアカウント名と差し替えてください。requests モジュールを使えば、簡単にHTTPリクエストが遅れます。

import requests as r

url = 'https://HOSTNAME'
res = r.get(url)
print(res.ok)

2. インスタンスへのREST APIへの疎通確認してください。
(対象APIやレスポンス値は問わない)

単純にAPIのURLにHTTPリクエストを送るだけです。requestsモジュールだと、ok関数が成否を示してくれます。

import requests as r

url = 'https://HOSTNAME/api/v1/instance'
res = r.get(url)
print(res.ok)

3. インスタンスへクライアント「Python_Exercise」を登録して、Client ID と Client Secret を取得してください。

Client ID と Client Secret は Mastodon.pyモジュールを使えば、1つの関数で1つのファイルに格納してくれます。以降の処理は全てこのファイルを使うので、非常に重要です。

from mastodon import Mastodon

url = 'https://HOSTNAME'
appname = 'Python_Exercise'
cid_file = 'client_id.txt'

Mastodon.create_app(
    appname,
    api_base_url=url,
    to_file = cid_file
)

4. インスタンスからアクセストークンを取得して、ファイル「access_token.txt」に保存してください。

今回はパスワード方式で登録します。実運用では推奨されていません。また、前問1-3で取得したClient IDおよびClient Secretを保存したファイルを利用します。同じく、以降の処理は全てここでのファイルを使うので、非常に重要です。

なお、[email protected] および PASSWORD もアカウントの正確なものと読み替えてください。※この方法が褒められたものでは無いとは思いますが。。。

from mastodon import Mastodon

url = 'HOSTNAME'
email = '[email protected]'
password = 'PASSWORD'
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    api_base_url=url,
)
mastodon.log_in(
    username = email,
    password = password,
    to_file = token_file
)

5. 上記で保存したアクセストークンを表示してください

前問1-4で取得したアクセストークンを保存したファイルを指定します。

token_file = 'access_token.txt'
with open(token_file) as ft:
    print(str(ft.read())
スポンサーリンク

解答2. トゥート投稿

以降は問1-3および1-4で作成した各ファイルを利用します。また、ホスト名の指定は引数で行うように変更します。

Pythonでの引数の取り扱い方は下記をご覧下さい。

2. Python インタプリタを使う — Python 3.6.5 ドキュメント

1. 「はじめてのトゥート #Python練習」とトゥート(投稿)してください。

toot関数いっぱつです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

mastodon.toot('はじめてのトゥート #Python練習')

2. 「2回目のトゥート #Python練習」というテキストと何か画像1つを添付してトゥート(投稿)してください。

画像ファイル「img1.png」をアップロードして、トゥートに添付します。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
imgfile1 = 'img1.png'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

img1 = mastodon.media_post(imgfile1)

img_files = [img1]
mastodon.status_post(
    status = 'はじめてのトゥート2 #Python練習',
    media_ids = img_files
    )

3. 「3回目のトゥート #Python練習」というテキストと何か画像4つを添付してトゥート(投稿)してください。

画像ファイル「img1.png」「img2.png」「img3.png」「img4.png」をアップロードして、トゥートに添付します。これらをmedia_post関数でアップして、その結果のリストを status_post関数に添付するのがミソです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
img_filenames = ['img1.png', 'img2.png', 'img3.png', 'img4.png']

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

imgs = []
for img in img_filenames:
    imgs.append(mastodon.media_post(img))

mastodon.status_post(
    status = '3回目のトゥート #Python練習',
    media_ids = imgs
    )

4. 「4回目のトゥート #Python練習」というテキストに、「CWの練習」という警告(CW)を付けて、トゥート(投稿)してください。

status_post関数の引数spoiler_textに値を渡してやるだけです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

mastodon.status_post(
    '4回目のトゥート #Python練習',
    spoiler_text='CWの練習'
    )

5. 「5回目のトゥート #Python練習」というテキストに、何か画像1つを添付して、NSFWで隠してトゥート(投稿)してください。

status_post関数の引数sensitiveにTrueを渡してやるだけで、あとは通常の画像投稿と同じです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
imgfile1 = 'img1.png'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

img1 = mastodon.media_post(imgfile1)

img_files = [img1]
mastodon.status_post(
    status = '5回目のトゥート #Python練習',
    sensitive=True,
    media_ids = img_files
    )

6. 「はじめての非掲載トゥート #Python練習」というテキストを、公開範囲「未収載」に設定してトゥート(投稿)してください。

status_post関数の引数visibilityに値’unlisted’を渡してやるだけです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

mastodon.status_post(
    'はじめての未収載トゥート #Python練習',
    visibility='unlisted'
    )

7. 「はじめての非公開トゥート #Python練習」というテキストを、公開範囲「非公開」に設定してトゥート(投稿)してください。

status_post関数の引数visibilityに値’private’を渡してやるだけです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

mastodon.status_post(
    'はじめての非公開トゥート #Python練習',
    visibility='private'
    )

8. 「@[email protected] はじめてのダイレクトトゥート #Python練習」というテキストを、公開範囲「ダイレクト」に設定してトゥート(投稿)してください。

status_post関数の引数visibilityに値’direct’を渡してやるだけです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

mastodon.status_post(
    '@[email protected] はじめてのダイレクトトゥート #Python練習',
    visibility='direct'
    )

9. 「@[email protected] トゥート練習中 #Python練習」というテキストを下記の設定でトゥート(投稿)してください。

・ CW / 警告文「CW練習中」を付与すること
・ 何らか画像を1つ添付 / NSFWを付与すること
・ 公開範囲は「非公開」

これまでの練習の内容を組み合わせただけです。特にヒネりもありません。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
imgfile1 = 'img1.png'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

img1 = mastodon.media_post(imgfile1)

img_files = [img1]
mastodon.status_post(
    status = '5回目のトゥート #Python練習',
    spoiler_text='CWの練習',
    sensitive=True,
    visibility='private',
    media_ids = img_files
    )

10. [email protected]に「メンション練習中 #Python練習」というテキストでメンションを送ってください。

練習4-8とほぼ一緒です。内容の最初にメンション先のアカウント名を入れるだけです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

mastodon.toot('@[email protected] メンション練習中 #Python練習')
スポンサーリンク

解答3. トゥート操作

Pythonを使って、トゥートに関する様々な操作ができるかを確認する問題です。

1. 練習2-1で投稿したトゥートにお気に入りを付けてください。

練習2-1と同じくトゥートの投稿後にすぐお気に入りをつけるところを再現します。別の方法として、アカウントの過去トゥートを遡って練習2-1の結果からお気に入りを付ける仕組みにしても構いません。

ポイントは account_verify_credentials関数の返値のJSONデータです。純粋なJSONデータでは無く、Mastodon.py特有の形式なので、若干扱いが違います。また、どの大量のキーがあるので、どれを扱うかはキチンと下記を参照する必要があります。

tootsuite/documentation
Full documentation repository for Mastodon. Contribute to tootsuite/documentation development by creating an account on GitHub.

ちなみに、以前の練習問題では説明をしてませんが、トゥートはAPI上では「status」です。こちらもお間違えなく。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 最初のトゥート
mastodon.toot('はじめてのトゥート #Python練習')

# 最新トゥートにお気に入りを付ける
user_dict = mastodon.account_verify_credentials()
user_toots_dict = mastodon.account_statuses(user_dict['id'], limit=1)
mastodon.status_favourite(user_toots_dict[0]['id'])

2. 上記で付けたお気に入りをトゥートから外してください。

練習3-1の実行直後に実行すること、つまり自分のアカウントの最新トゥート1件が対象になることを前提にしています。もちろん、アカウントの過去トゥートを遡って練習2-1の結果から、付けたお気に入りを解除する仕組みにしても構いません。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 最新トゥートのお気に入りを解除する
user_dict = mastodon.account_verify_credentials()
user_toots_dict = mastodon.account_statuses(user_dict['id'], limit=1)
mastodon.status_unfavourite(user_toots_dict[0]['id'])

3. 練習2-1で投稿したトゥートをブーストしてください。

練習3-2の実行直後に実行すること、つまり自分のアカウントの最新トゥート1件が対象になることを前提にしています。もちろん、アカウントの過去トゥートを遡って練習2-1の結果からブーストする仕組みにしても構いません。

ちなみに、日本語では「ブースト」と呼んでいますが、API上では「reblog」という表現であることに注意してください。リファレンスをboostで検索したりしてもヒットしません。。。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 最新トゥートをブーストする
user_dict = mastodon.account_verify_credentials()
user_toots_dict = mastodon.account_statuses(user_dict['id'], limit=1)
mastodon.status_reblog(user_toots_dict[0]['id'])

4. 上記でブーストしたトゥートの、ブーストを解除してください。

練習3-3の実行直後に実行すること、つまり自分のアカウントの最新トゥート1件が対象になることを前提にしています。もちろん、アカウントの過去トゥートを遡って練習2-1の結果からブーストを解除する仕組みにしても構いません。

ただ、注意として、このブースト解除関数unreblogは少しクセがあります。ブースト後のトゥートでは無く、ブースト元のトゥートのIDを指定しないとエラーになります。また、ブースト解除関数の実行後、反映されるには若干のタイムラグが発生します。(詳細不明)

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 最新のトゥートをブースト解除する
user_dict = mastodon.account_verify_credentials()
user_toots_dict = mastodon.account_statuses(user_dict['id'], limit=1)
mastodon.status_unreblog(user_toots_dict[0]['id'])

5. 練習2-1で投稿したトゥートを削除してください。

練習3-4の実行直後に実行すること、つまり自分のアカウントの最新トゥート1件が対象になることを前提にしています。もちろん、アカウントの過去トゥートを遡って練習2-1の結果からブーストを解除する仕組みにしても構いません。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)


# 最新のトゥートを削除する
user_dict = mastodon.account_verify_credentials()
user_toots_dict = mastodon.account_statuses(user_dict['id'], limit=1)
mastodon.status_delet(user_toots_dict[0]['id'])
スポンサーリンク

解答4. 自分のアカウント情報の取得

1. アカウント情報を取得して、JSON形式で表示しなさい。

練習3で既に何度も使っていたコードですから、サービス問題です。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# アカウント情報を取得・表示する
user_dict = mastodon.account_verify_credentials()
print(user_dict)

2. 自分のアカウント名(アカウント名@インスタンス名)のみ表示しなさい。

問4-1の表示結果のJSONデータは辞書型として扱って、キー’username’の値を取り出します。表示させるときにホスト名をくっつけるのを忘れずに。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# アカウント名を表示する
user_dict = mastodon.account_verify_credentials()
print(user_dict['username'] + '@' + url)

3. 自分の表示名のみ表示しなさい。

問4-1の表示結果のJSONデータは辞書型として扱って、キー’display_name’の値を取り出します。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# アカウントの表示名を表示する
user_dict = mastodon.account_verify_credentials()
print(user_dict['display_name'])

4. 自分のプロフィールの内容のみ表示しなさい。

問4-1の表示結果のJSONデータは辞書型として扱って、キー’note’の値を取り出します。今回はタグを取り除いたり、変換するような指定はありませんが、実際に使う場合にはHTMLタグの取り扱いに注意が必要です。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 自分のアカウントのプロフィールの内容を表示する
user_dict = mastodon.account_verify_credentials()
print(user_dict['note'])

5. 自分のフォロー数のみ表示しなさい。

問4-1の表示結果のJSONデータは辞書型として扱って、キー’following_count’の値を取り出します。結果をprint文に入れる時は文字列型へ変換する必要がある点は注意です。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 自分のアカウントのフォロー数を表示する
user_dict = mastodon.account_verify_credentials()
print(str(user_dict['following_count']))

6. 自分のフォロワー数のみ表示しなさい。

問4-1の表示結果のJSONデータは辞書型として扱って、キー’following_count’の値を取り出します。結果をprint文に入れる時は文字列型へ変換する必要がある点は注意です。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 自分のアカウントのフォロワー数を表示する
user_dict = mastodon.account_verify_credentials()
print(str(user_dict['followers_count']))

7. 自分のお気に入り数のみ表示しなさい。

実は、本記事の中で最難関とも言える練習問題です。

問4-1の表示結果のJSONデータには「お気に入りをした数」は含まれないのがポイントです。そのため、お気に入りを全件取り出して数えなくてはならないのですが、Mastodon APIの下記仕様があるため、けっこう難しいです。

  • いずれのメソッドも、トゥートのリストを取得するには一回に40件までしか取得できません。そのため、全件取得するには何度もページネーション(ページ繰り)を行う必要があります。
  • Mastodon API へのリクエストは、いずれも5分で300リクエスト以内に抑える必要があります。その量を超えるとエラーが返されてしまいます。
  • これは API 仕様というより、マナーという意味合いが大きいのですが、リクエストを送るインスタンスに大きな負荷がかからないように注意しなくてはなりません。そのためには、短時間での大量のリクエストは避ける必要があります。

上記を制約を考慮した場合、requestsモジュールなどで地道に作るには複雑なコードになりがちです。しかし、Mastodon.py のような専用モジュールを使うとシンプルなコードにできまうので便利です。例えば、Mastodon.py で ratelimit_method に pace を指定すれば、いい感じにリクエスト間の時間を空けてくれます。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id_pawoo.txt'
token_file = 'access_token_pawoo.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 自分のアカウントのお気に入り数を数える
next_id = ''
fav_count = 0

# 自分のアカウントのお気に入り数を数える
while True:
    # ローカルタイムラインの取得
    fetched_favs = mastodon.favourites(limit=40, max_id=next_id)

    # 新しいトゥートが取得できなかったらループ終了
    if len(fetched_favs) > 0:
        fav_count += len(fetched_favs)
    else:
        break

    # 80件以上のページネーションするための値取得
    favs_last = fetched_favs[len(fetched_favs)-1]
    if '_pagination_next' in favs_last.keys() and 'max_id' in favs_last['_pagination_next'].keys():
        next_id = favs_last['_pagination_next']['max_id']
    else:
        break

# お気に入り数を表示
print(fav_count)

8. 自分の最新トゥート10件をJSON形式で表示しなさい。

練習3-1が回答できていれば簡単です。JSON値をそのままprintしてあげればOKです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 自分の最新トゥート10件を取得する
user_dict = mastodon.account_verify_credentials()
user_toots = mastodon.account_statuses(user_dict['id'], limit=10)

# トゥートを全て表示する
print(user_toots)

9. 自分の最新トゥート1件の時間と内容のみ表示しなさい。

練習4-8の結果のうち、対象が created_at と content の値とさえ分かれば簡単です。なお、通常はcontentはHTMLタグが含まれます。今回はタグを取り除いたり、変換するような指定はありませんが、実際に使う場合にはHTMLタグの取り扱いに注意が必要です。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 自分の最新トゥート1件を取得する
user_dict = mastodon.account_verify_credentials()
user_toots_dict = mastodon.account_statuses(user_dict['id'], limit=1)

# トゥートの時間と内容を表示する
print(user_toots[0]['created_at'], user_toots[0]['content'])

10. 自分の最新トゥート1件のアプリ名のみ表示しなさい。

実は、ひっかけ問題です。Mastodon APIのクセが垣間見える問題です。

練習4-8の結果のうち、対象が application 値の中の name 値とさえ分かれば簡単なように見えて、次の法則があります。

  • ローカルフォローしている相手が Web 、つまり標準のWebクライアントを使用している場合、値が None になる。
  • リモートフォローしている相手の場合、値が None になる。この場合、クライアント情報を取得できないことを意味する。
  • ブースト(reblog)されたトゥートだと、指定すべきキーの位置がreblogキ一の下になる。

そのため、値のチェックおよび相手によって、表示処理の判定を分ける必要があります。ただし、今回は自分のトゥートだけなので前者だけを対象にします。

なお、ブーストの有無によって、同じ処理が繰り返される箇所が発生するので、関数にまとめた都合で、Python的なメイン関数の書き方を用いました。もしご存知ない方は、これを機に下記をご覧になると良いかもしれません。

__main__ --- トップレベルのスクリプト環境 — Python 3.7.2 ドキュメント
Python 「if __name__ == ‘__main__’:」の意味 - Qiita
## モジュール Pythonのコードはスクリプトファイルとして保存し、他のプログラムから再利用することができます。そのファイルのことをモジュールという。 「import」を使用することで自作したモジュールもimportを使って読み...
import sys
from mastodon import Mastodon

URL = sys.argv[1]
CID_FILE = 'client_id.txt'
TOKEN_FILE = 'access_token.txt'


def check_appname(toot):
    ''' アプリケーション名をチェックするルーチン '''
    if toot['application'] and toot['application']['name']:
        return(toot['application']['name'])
    elif toot['application'] and not toot['application']['name']:
        return('Web')
    else:
        return('Unknown')
        

def main():
    ''' メインルーチン '''
    # Mastodon初期化
    mastodon = Mastodon(
        client_id=cid_file,
        access_token=token_file,
        api_base_url=url
    )
    
    # 自分の最新トゥート1件を取得する
    user_dict = mastodon.account_verify_credentials()
    user_toots = mastodon.account_statuses(user_dict['id'], limit=1)
    
    # トゥートのアプリ名があれば表示する
    if user_toots[0]['reblog'] is None:
        print(check_appname(user_toots[0]))           # 通常のトゥートの場合
    else:
        print(check_appname(user_toots[0]['reblog'])) # ブーストされたトゥートの場合


if __name__ == '__main__':
    main()
スポンサーリンク

解答5. 他人のアカウント情報の取得

1. アカウント「いつもの匠([email protected])」を検索して、その結果をJSON形式で表示しなさい。

他人のアカウントID(≠アカウント名)をどう取得するかがポイントです。利用しているインスタンスの設定にも依存しますが、今回は対象アカウントを検索できることを前提とします。(予め問題文に記載がなく、申し訳ありませんでした。)

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントの情報を表示する。
user_dict = mastodon.account(user_id)
print(user_dict)

2. アカウント「いつもの匠([email protected])」の表示名を表示しなさい。

問題5-1が解けていれば楽勝のはずです。問5-1のJSONデータのキー’display_name’の値を取り出します。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントの表示名を表示する。
user_dict = mastodon.account(user_id)
print(user_dict['display_name'])

3. アカウント「いつもの匠([email protected])」のプロフィール内容のみ表示しなさい。

問題5-1が解けていれば楽勝のはずです。問5-1のJSONデータのキー’note’の値を取り出します。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントの表示名を表示する。
user_dict = mastodon.account(user_id)
print(user_dict['display_name'])

4. アカウント「いつもの匠([email protected])」のフォロー数のみ表示しなさい。

こちらも問題5-1が解けていれば楽勝のはずです。問5-1のJSONデータのキー’following_count’の値を取り出します。ただし、Mastodon の仕様で、フォロー数が正確に表示されなかったりします。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントのフォロー数を表示する。
user_dict = mastodon.account(user_id)
print(user_dict['following_count'])

5. アカウント「いつもの匠([email protected])」のフォロワー数のみ表示しなさい。

こちらも問題5-1が解けていれば楽勝のはずです。問5-1のJSONデータのキー’followers_count’の値を取り出します。ただし、Mastodon の仕様で、フォロワー数が正確に表示されなかったりします。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントのフォロワー数を表示する。
user_dict = mastodon.account(user_id)
print(user_dict['followers_count'])

6. アカウント「いつもの匠([email protected])」のお気に入り数のみ表示しなさい。

実は、これはちょっと問題のミスです。マストドンでは、他人のお気に入り内容とその数を確認することができません。そのため、この問題の正解は表示できないです。

もし解決方法をご存知の方がいらっしゃれば、ご教授ください。。。

7. アカウント「いつもの匠([email protected])」の最新トゥート10件をJSON形式で表示しなさい。

練習4-8と練習5-1の組み合わせです。対象アカウントを検索してユーザーIDを取得した後、そのユーザーIDを使って最新トゥートを取得します。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントの最新トゥート10件を取得する
user_toots = mastodon.account_statuses(user_id, limit=10)

# トゥートを全て表示する
print(user_toots)

8. アカウント「いつもの匠([email protected])」の最新トゥート1件の投稿時間と内容を表示しなさい。

問題4-9と問題5-1の組み合わせです。対象アカウントを検索してユーザーIDを取得した後、そのユーザーIDを使って最新トゥートを取得するまでは問題5-7と同じです。

なお、上記でもお伝えした通り、通常はcontentはHTMLタグが含まれます。今回はタグを取り除いたり、変換するような指定はありませんが、実際に使う場合にはHTMLタグの取り扱いに注意が必要です。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントの最新トゥート1件を取得する
user_toots = mastodon.account_statuses(user_id, limit=1)

# トゥートの時間と内容を表示する
print(user_toots[0]['created_at'], user_toots[0]['content'])

9. アカウント「いつもの匠([email protected])」の最新トゥート1件のアプリ名のみ表示しなさい。

問題4-10と問題5-1の組み合わせです。対象アカウントを検索してユーザーIDを取得した後、そのユーザーIDを使って最新トゥートを取得するまでは問題5-7や問題5-8と同じです。

import sys
from mastodon import Mastodon

URL = sys.argv[1]
CID_FILE = 'client_id.txt'
TOKEN_FILE = 'access_token.txt'
USERNAME = '[email protected]'

def check_appname(toot):
    ''' アプリケーション名をチェックするルーチン '''
    if toot['application'] and toot['application']['name']:
        return(toot['application']['name'])
    elif toot['application'] and not toot['application']['name']:
        return('Web')
    else:
        return('Unknown')

def main():
    ''' メインルーチン '''
    # Mastodon初期化
    mastodon = Mastodon(
        client_id=cid_file,
        access_token=token_file,
        api_base_url=url
    )

    # 対象アカウントのユーザーIDを取得する。
    user_list = mastodon.account_search(USERNAME, limit=1)
    user_id = user_list[0]['id']

    # 対象アカウントの最新トゥート1件を取得する
    user_toots = mastodon.account_statuses(user_id, limit=1)
        
    # トゥートのアプリ名があれば表示する
    if user_toots[0]['reblog'] is None:
        print(check_appname(user_toots[0]))           # 通常のトゥートの場合
    else:
        print(check_appname(user_toots[0]['reblog'])) # ブーストされたトゥートの場合

if __name__ == '__main__':
    main()

10. アカウント「いつもの匠([email protected])」の最新トゥート1件のお気に入りがついているか、ついているならお気に入り数を表示しなさい。

お気に入りがついていれば数、お気に入りが付いていなければ「Not favourited」と表示する仕組みにします。トゥートの’favourites_count’キーの値だけでも判断ができますが、’favourited’キーで有無の判断が簡単にできます。正答のコードでは’favourited’キーで判断する仕組みを採用しています。

ただし、Mastodonの仕様上、別インスタンスのアカウントのトゥート情報は正確に取得はできないため、取得した値が実際と異なる場合が多々あります。これはMastodonの思想によるものであって、不具合ではないようです。。。

なお、Pythonの文法としては、if文でBoolean型の変数のみ条件式に入れることで、条件式を省略しているのがポイントです。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
username = '[email protected]'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 対象アカウントのユーザーIDを取得する。
user_list = mastodon.account_search(username, limit=1)
user_id = user_list[0]['id']

# 対象アカウントの最新トゥート1件を取得する
user_toots = mastodon.account_statuses(user_id, limit=1)

# トゥートにお気に入り数によって処理を分岐する。
if user_toots[0]['favourited']:
    print(user_toots[0]['favourites_count'])
else:
    print('Not favourited')
スポンサーリンク

解答6. タイムライン表示

1. インスタンスのホスト名(FQDN)を表示しなさい。

実際のところは引数で入力しているので必要ないのですが、それでは練習にならないので、本問題ではinstance関数を使って取得した instance データの’uri’キーを表示します。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスのドメイン名を表示する
instance = mastodon.instance()
print(instance['uri'])

2. インスタンスの説明のみ表示しなさい。

こちらも簡単です。instance関数を使って取得した instance データの’description’キーを表示します。ちなみに、Webスクレイピングすれば、必ずしもMastodonアカウントが無くても取得できますね。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスの説明を表示する
instance = mastodon.instance()
print(instance['description'])

3. インスタンスのMastodonのバージョンのみ表示しなさい。

instance関数を使って取得した instance データの’version’キーを表示します。こちらもWebスクレイピングすれば、必ずしもMastodonアカウントが無くても取得できますね。

import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスの説明を表示する
instance = mastodon.instance()
print(instance['version'])

4. インスタンスのローカルタイムラインのトゥートを10件表示しなさい。

Mastodon.pyモジュールの timeline_local関数を使ってシンプルに取得できますね。

特に形式は指定してませんでしたが、Pythonの練習のために整形やrange関数を使ってみました。

  • reモジュールで正規表現を使ってトゥートのタグ削除する
  • format関数で表示内容を整形する
  • for文とrange関数の組み合わせで配列を扱う
import sys
import re
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
conv = re.compile(r"<[^>]*?>")
toot_num = 10  # 取得するトゥートの件数

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスのローカルタイムラインのトゥートを10件表示する
toots = mastodon.timeline_local()
for i in range(0, toot_num):
    toot_text = conv.sub("", toots[i]['content'])
    print("{:%Y-%m-%d %H:%M:%S}, {}".format(toots[i]['created_at'], toot_text))

ちなみに、実験してみたことがあるのですが、アクセストークンが無くてもローカルタイムラインのトゥートは取得できます。ただ、アクセストークンがある場合よりも件数が減ってしまっていたので、なんらかの理由でトゥートが漏れてしまうみたいですね。

5. インスタンスの連合タイムラインのトゥートを10件表示しなさい。

問題6-4のコードを流用すれば簡単です。timeline_local関数を timeline_public関数に置き換えるだけです。

import sys
import re
import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
conv = re.compile(r"<[^>]*?>")
toot_num = 10  # 取得するトゥートの件数

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスのローカルタイムラインのトゥートを10件表示する
toots = mastodon.timeline_public()
for i in range(0, toot_num):
    toot_text = conv.sub("", toots[i]['content'])
    print("{:%Y-%m-%d %H:%M:%S}, {}".format(toots[i]['created_at'], toot_text))

6. ハッシュタグ「#」のついたトゥートを最新10件のみ表示しなさい。

問題6-4のコードを流用すれば簡単です。timeline_local関数を timeline_hashtag関数に置き換えてみます。また、特にハッシュタグを指定してはいませんでしたので、今回は「#こんなマストドンは嫌だ」を対象にしてみます。

なお、取得できるハッシュタグ付きトゥートはローカルのみ、または連合しているインスタンスのものかを選べます。後者の場合、連合しているインスタンスだけなので、アカウントによって取得できるトゥートにバラつきがある点は留意しておく必要があります。

import sys
import re
import sys
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
conv = re.compile(r"<[^>]*?>")
toot_num = 10  # 取得するトゥートの件数
hashtag = 'こんなマストドンは嫌だ'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスのローカルタイムラインのトゥートを10件表示する
toots = mastodon.timeline_hashtag(hashtag)
for i in range(0, toot_num):
    toot_text = conv.sub("", toots[i]['content'])
    print("{:%Y-%m-%d %H:%M:%S}, {}".format(toots[i]['created_at'], toot_text))

7. 自分が所属するインスタンスのローカルタイムラインのトゥートを100件表示しなさい。

ここから急に難易度が上がります。一回に40件しか取得できないため、何回かページネーション(ページめくり)が必要です。その上で、取得したページ数が40件に満たない場合の処理など、いくつか判定が必要になります。そういった理由から、少しコードが複雑になりました。

import re
import sys
from mastodon import Mastodon
    
url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
conv = re.compile(r"<[^>]*?>")
toot_num = 100  # 取得するトゥートの件数

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスのローカルタイムラインのトゥートを100件表示する
next_id = ''
toots = []

while (len(toots) < toot_num):
    # ローカルタイムラインの取得
    fetched_toots = mastodon.timeline(
        timeline='local',
        limit=80,
        max_id=next_id
    )
    
    # 新しいトゥートが取得できなかったらループ終了
    if len(fetched_toots) > 0:
        toots += fetched_toots
    else:
        break

    # 80件以上のページネーションするための値取得
    toots_last = len(toots)-1
    if toots[toots_last]['_pagination_next']['max_id'] is not None:
        next_id = toots[toots_last]['_pagination_next']['max_id']
    else:
        break

# 表示するトゥート数の上限を指定
max_len = toot_num if toot_num < len(toots) else len(toots)

# トゥートを整形して表示する
for i in range(0, max_len):
    toot_text = conv.sub("", toots[i]['content'])
    print("{}, {:%Y-%m-%d %H:%M:%S}, {}".format(
        i+1,
        toots[i]['created_at'],
        toot_text
        )
    )

8. 自分が所属するインスタンスの連合タイムラインのトゥートを100件表示しなさい。

こちらは練習6-7とほとんどコードが同じです。ただし、timeline関数の引数timelineの値をpublicに指定するだけです。

import sys
import re
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
conv = re.compile(r"<[^>]*?>")
toot_num = 100  # 取得するトゥートの件数

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスの連合タイムラインのトゥートを100件表示する
next_id = ''
toots = []

while (len(toots) < toot_num):
    # 連合タイムラインの取得
    fetched_toots = mastodon.timeline(
        timeline='public',
        limit=40,
        max_id=next_id
    )
    
    # 新しいトゥートが取得できなかったらループ終了
    if len(fetched_toots) > 0:
        toots += fetched_toots
    else:
        break

    # 80件以上のページネーションするための値取得
    toots_last = len(toots)-1
    if toots[toots_last]['_pagination_next']['max_id'] is not None:
        next_id = toots[toots_last]['_pagination_next']['max_id']
    else:
        break

# 表示するトゥート数の上限を指定
max_len = toot_num if toot_num < len(toots) else len(toots)

# トゥートを整形して表示する
for i in range(0, max_len):
    toot_text = conv.sub("", toots[i]['content'])
    print("{}, {:%Y-%m-%d %H:%M:%S}, {}".format(
        i+1,
        toots[i]['created_at'],
        toot_text
        )
    )

9. ハッシュタグ「#」のついたトゥートを最新100件のみ表示しなさい。

こちらは練習6-8とほとんどコードが同じですが、timeline_hashtag関数を使用する箇所だけが異なります。

import sys
import re
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client.txt'
token_file = 'access_token.txt'
conv = re.compile(r"<[^>]*?>")
toot_num = 100  # 取得するトゥートの件数
hashtag = 'こんなマストドンは嫌だ'  # ハッシュタグの指定

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# 指定したハッシュタグのついたトゥートを100件表示する
next_id = ''
toots = []

while (len(toots) < toot_num):
    # ハッシュタグのついたトゥートの取得
    fetched_toots = mastodon.timeline_hashtag(
        hashtag=hashtag,
        local=False,
        limit=40,
        max_id=next_id
    )
    
    # 新しいトゥートが取得できなかったらループ終了
    if len(fetched_toots) > 0:
        toots += fetched_toots
    else:
        break

    # 80件以上のページネーションするための値取得
    toots_last = len(toots)-1
    if toots[toots_last]['_pagination_next']['max_id'] is not None:
        next_id = toots[toots_last]['_pagination_next']['max_id']
    else:
        break

# 表示するトゥート数の上限を指定
max_len = toot_num if toot_num < len(toots) else len(toots)

# トゥートを整形して表示する
for i in range(0, max_len):
    toot_text = conv.sub("", toots[i]['content'])
    print("{}, {:%Y-%m-%d %H:%M:%S}, {}".format(
        i+1,
        toots[i]['created_at'],
        toot_text
        )
    )

10. 自分が所属するインスタンスのローカルタイムラインのトゥート200件表示しなさい。
表示形式: 時間/アカウント名/トゥート内容

問題6-7から流用して、取得するトゥートの件数を200に指定するだけです。出力形式が少し異なるため、書き換えが必要な点だけは注意ですが、大したことはありません。

import sys
import re
from mastodon import Mastodon

url = sys.argv[1]
cid_file = 'client_id.txt'
token_file = 'access_token.txt'
conv = re.compile(r"<[^>]*?>")
toot_num = 200  # 取得するトゥートの件数

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# インスタンスのローカルタイムラインのトゥートを200件表示する
next_id = ''
toots = []

while (len(toots) < toot_num):
    # ローカルタイムラインの取得
    fetched_toots = mastodon.timeline(
        timeline='local',
        limit=40,
        max_id=next_id
    )
    
    # 新しいトゥートが取得できなかったらループ終了
    if len(fetched_toots) > 0:
        toots += fetched_toots
    else:
        break

    # 80件以上のページネーションするための値取得
    toots_last = len(toots)-1
    if toots[toots_last]['_pagination_next']['max_id'] is not None:
        next_id = toots[toots_last]['_pagination_next']['max_id']
    else:
        break

# 表示するトゥート数の上限を指定
max_len = toot_num if toot_num < len(toots) else len(toots)

# トゥートを整形して表示する
for i in range(0, max_len):
    toot_text = conv.sub("", toots[i]['content'])
    print("{}, {:%Y-%m-%d %H:%M:%S}/{}/{}".format(
        i+1,
        toots[i]['created_at'],
        toots[i]['account']['username'],
        toot_text
        )
    )
スポンサーリンク

おわりに

なんとか50個の問題を作り終えました。いかがでしたでしょうか。

正直なところ、飛行機や電車での移動中など、様々な場所で作っていました。そのくらい時間が無かったのですが、下記のiPadアプリが大活躍してくれました。

Pythonista 3
Pythonista 3
Developer: omz:software
Price: ¥1,200

場所を選ばず、パソコン無しで、この記事(Markdown形式)とソースコードの作成&テストの全てをこなすことができました。ホントに便利な世の中になりましたね。非常に助かりました。

なお、今回の内容やソースコードが動かないなど、ご意見は下記にご連絡ください。

このエントリーをはてなブックマークに追加
Twitter: @itsumonotakumi
Mastodon: @[email protected]