酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Mastodon .py を使って画像を添付したステータスを Mastodon へ投稿する

Python を使って、画像を添付したステータス「Hello, Mastodon!」を Mastodon へ投稿する方法を紹介する。

Mastodon.py のインストール

Python で Mastodon にメッセージを投稿するには Mastodon.py というパッケージを使用する。Python から Mastodon API に簡単にアクセスするためのツールである。

github.com

このパッケージは pip を使ってインストールできる。

pip install Mastodon.py

Mastodon.py を使って画像を添付したステータスを Mastodon へ投稿する

以前、さくさんは Pawoo を使用していたが、運営会社の移管や2023年2月のTwitterでの大規模なアカウント凍結事案により、サーバーが過負荷になり、タイムラインがまともに読めなくなってしまった。そのため現在では Fedibird を主要なサーバーとして使用している。

この例では api_base_urlhttps://fedibird.com を指定しているが、皆さんが利用しているサーバーのAPIベースURLに変更して欲しい。

import os
import sys
from mastodon import Mastodon

access_token = 'アクセストークン'
client_id = 'クライアントID'
client_secret = 'クライアントシークレット'
api_base_url = 'https://fedibird.com'

mastodon = Mastodon(
    access_token=access_token, 
    client_id=client_id, 
    client_secret=client_secret, 
    api_base_url=api_base_url
)

# 引数から画像を含むディレクトリのパスを取得する
image_dir_path = sys.argv[1]

# 画像をアップロードする
media_ids = []
for filename in sorted(os.listdir(image_dir_path)):
    if filename.endswith('.png') or filename.endswith('.jpg'):
        file_path = os.path.join(image_dir_path, filename)
        media = mastodon.media_post(file_path)
        media_id = media['id']
        print(f"アップロードしたファイル: {file_path}, メディアID: {media['id']}")
        media_ids.append(media_id)

# 画像のアップロード後、すぐにステータスを投稿しようとするとステータスコード 422 が返ってくるので10秒待機
time.sleep(10)

status = 'Hello, Mastodon!'
mastodon.status_post(status=status, media_ids=media_ids, visibility='public')

ターミナルで、アップロードしたい画像ファイルが入っているディレクトリパスを指定して実行しよう。

python post_media_mastodon.py /Users/ch3cooh/Desktop/images/20230412

関連記事

Mastodon .py を使ってステータスを Mastodon へ投稿する

Python を使って、Mastodonに「Hello, Mastodon!」と投稿する方法を紹介する。

Mastodon.py のインストール

Python で Mastodon にメッセージを投稿するには Mastodon.py というパッケージを使用する。Python から Mastodon API に簡単にアクセスするためのツールである。

github.com

このパッケージは pip を使ってインストールできる。

pip install Mastodon.py

Mastodon.py を使ってステータスを Mastodon へ投稿する

以前、さくさんは Pawoo を使用していたが、運営会社の移管や2023年2月のTwitterでの大規模なアカウント凍結事案により、サーバーが過負荷になり、タイムラインがまともに読めなくなってしまった。そのため現在では Fedibird を主要なサーバーとして使用している。

この例では api_base_urlhttps://fedibird.com を指定しているが、皆さんが利用しているサーバーのAPIベースURLに変更して欲しい。

import os
import sys
from mastodon import Mastodon

access_token = 'アクセストークン'
client_id = 'クライアントID'
client_secret = 'クライアントシークレット'
api_base_url = 'https://fedibird.com'

mastodon = Mastodon(
    access_token=access_token, 
    client_id=client_id, 
    client_secret=client_secret, 
    api_base_url=api_base_url
)

status = 'Hello, Mastodon!'
mastodon.status_post(status=status, visibility='public')

上記のスクリプトを実行した。成功すると下図のようにトゥートできているはずだ。

関連記事

Python で指定したディレクトリを再起的に調べていき、画像が破損している場合にファイルを削除する

Python で指定したディレクトリに含まれる画像が破損している場合に当該の画像ファイルを削除する方法を紹介する。

はじめに

Google Colab での TensorFlow を使った転移学習時に以下のエラーが発生した。

/usr/local/lib/python3.9/dist-packages/tensorflow/python/eager/execute.py in quick_execute(op_name, num_outputs, inputs, attrs, ctx, name)
     50   try:
     51     ctx.ensure_initialized()
---> 52     tensors = pywrap_tfe.TFE_Py_Execute(ctx._handle, device_name, op_name,
     53                                         inputs, attrs, num_outputs)
     54   except core._NotOkStatusException as e:

InvalidArgumentError: Graph execution error:

Input is empty.
     [[{{node decode_image/DecodeImage}}]]
     [[IteratorGetNext]] [Op:__inference_train_function_17834]

InvalidArgumentError: Graph execution error:DecodeImage から、画像ファイルの読み込みに失敗したのだろうかと疑った。具体的にどの画像ファイルが壊れているのかわからなかったが、画像が壊れている理由には心当たりがある。

Google Colab ではストレージの代わりとして Google Drive を利用する。機械学習させるために 160GB を超える大量の画像ファイルを Google Drive へアップロードしている。あまりにも大量だったからなのか、ファイルの保存(アップロード)時に画像ファイルが破損してしまったのだろう。

Google Drive へのアップロード時の問題に関しては以下のエントリに書いている。

blog.ch3cooh.jp

壊れた画像がデータセットに含まれているからエラーが発生するのだろうとアタリはつけたが、具体的に壊れているファイル名が分からず、画像ファイル数も多く検証が困難である。

Python で指定したディレクトリに含まれる画像が破損している場合に当該の画像ファイルを削除することにした。

指定したディレクトリを再起的に調べていき、画像が破損している場合にファイルを削除する

指定したディレクトリを再起的に調べていき、画像が破損している場合にファイルを削除するためには考えるべきことが二つある。

  • 画像ファイルの検証はどうするのか?
  • 画像ファイルを削除するにはどうするのか?

前者に関しては PIL (Python Imaging Library) の Image#verify() が使える。後者に関しては os.remove() でファイル削除ができる。

import os
from PIL import Image

def verify_images_recursive(directory):
    for root, dirs, files in os.walk(directory):
        for file in files:
            file_path = os.path.join(root, file)
            try:
                img = Image.open(file_path)
                img.verify()
            except Exception as e:
                print(f"エラー: {e}")
                os.remove(file_path)

dir_path = '/content/drive/MyDrive/colab_data/dataset'
verify_images_recursive(dir_path)

エラーを検出すると、例外が投げられて以下のようなメッセージとともに当該の画像ファイルを削除する。

エラー: cannot identify image file '/content/drive/MyDrive/colab/dataset/xxxxx/yyyyy.JPEG'

Colab でマウントしている Google Drive に対してファイル書き込み後、書き込んだランタイムからは参照できるのに、別のランタイムからはファイルが見えない

Google Colaboratory (以下Colabと記す) に Google Drive をマウントし、大きい .tar ファイルを解凍して Google Drive に保存した。解凍をした Colab ランタイム側ではファイルが確認できるが、別のランタイムからは解凍したファイルが見られないという問題が起こっている。

Colab を使い始めた初日だったため、Google Drive を外付けハードディスクのように考えており、ファイルの保存に成功すれば当然保存されていると思っていた。

様々なことに2日間頭を悩ませた結果、やっと状況を理解できるようになり、以下のように疑問を整理した。

Google Drive に保存したにもかかわらず、別のランタイムや Web 版 Google Drive からも解凍したファイルが見えないのである。つまり、ファイルが同期されていないという問題である。「Colab Google Drive Sync」というキーワードで検索した結果、以下のIssueが見つかった。

github.com

この現象は 2018年から発生しているようで、大容量や大量のファイルを Google Drive に書き込んでも保存されていないという問題が指摘されている。解決策として「drive.flush()を実装して欲しい!」と多数要求されていたが、2023年3月時点でもまだ実装されていない。現状では drive.flush_and_unmount() で代用する必要があるようだ。

from google.colab import drive

for tar_file in tar_files:

    # 解凍処理

    # 解凍したファイル群を Google Drive に同期させる
    drive.flush_and_unmount()
    drive.mount('/content/drive', force_remount=False)

解凍するたびに unmount と mount が実行されるため、処理速度はかなり低下する。悲しいことに 1分間に 1.5ファイル分しか解凍できないが、確実に Google Drive にファイルを保存できるようになった。

Cloudflare R2 で静的サイトを公開する

CloudFront + S3 で https://ch3cooh.net/ をホストしている。このサイトでは単に welcome! としか表示しない。特に何をやっているわけではなく、AdMob で規定されている 「アプリ向けの認定販売者宣言システム(app-ads.txt)」置き場である。そのためできる限りコストを掛けずに運用したい。

ch3cooh.netドメインは先日 AWS Route 53 から Cloudflare へ移管したので、CloudFront + S3で運用している静的サイトも Cloudflare + Worker + R2 に移行することにした。

R2 にバケットを作成して、htmlファイルをアップロードする

バケット名は net-ch3cooh として、S3に置いているファイルをそのまま持ってきた。

このバケットを直接インターネットに公開することは可能であるが、https://ch3cooh.net/ にアクセスするとエラーになってしまう。S3 では / にアクセスすると index.html を表示してくれたのだが……

Cloudflare で、S3でいうところのインデックスドキュメント機能 (/ にアクセスして index.html を表示させる機能) を使うためには Worker を使う必要がある。

本記事では Cloudflare + Worker + R2 で静的サイトを公開する方法を紹介する。

マイアカウントページで API Key を作成する

API Keyを作成する。API Keyは一度しか表示されないので忘れないようにする。

https://ch3cooh.net/ でアクセスしたときの証明書も自動で設定してくれるので「SSL証明書」、指定したドメインにアクセスされたときに Worker を割り当てるために「DNS」の編集権限が必要になるようだ。

Denoflare をインストールする

Denoflare を使って Cloudflare を CLI から操作できる。インストール方法は「CLI · Denoflare」より。

brew install deno
deno install --unstable --allow-read --allow-net --allow-env --allow-run --name denoflare --force \
https://raw.githubusercontent.com/skymethod/denoflare/v0.5.11/cli/cli.ts

/Users/ch3cooh/.zprofile で deno へのパスを通す。

export PATH="/Users/ch3cooh/.deno/bin:$PATH"

ターミナルを起動し直して deno にパスが通っていることを確認する。

Denoflare 経由で Worker を作成する

https://blog.3qe.us/entry/2023/01/15/015921 を参考にして Worker を追加する。

$ denoflare push \
  https://raw.githubusercontent.com/skymethod/denoflare/v0.5.11/examples/r2-public-read-worker/worker.ts \
  --name ${Workerの名前} \
  --r2-bucket-binding bucket:${R2バケット名} \
  --text-binding flags:listDirectories,emulatePages \
  --text-binding 'allowCorsOrigins:*' \
  --custom-domain ${ドメイン} \
  --account-id ${アカウントID} \
  --api-token ${APIトークン}
  • Workerの名前 は適当に worker-net-ch3cooh とした
  • R2バケット名 は さきほど作成した net-ch3cooh を指定した
  • ドメイン名 は公開したい ch3cooh.net を指定した

また「常にHTTPSを使用」を有効にしておくと、http://ch3cooh.net にアクセスされた場合に Cloudflare側で自動で https://ch3cooh.net にリダイレクトしてくれる。これだけでひとつページルールが節約になるので設定しておくと良い。

以上で、Cloudflare R2 のバケットを静的サイトをして公開することができるようになった。

参考ページ

blog.3qe.us

tks2.co.jp

Cloudflare でカスタムメールアドレスを作成して任意のメールアドレスへ転送する

ドメインを所有している証明として info@example.comwebmaster@example.com にメールを送ってくるサービスがあった。このためだけに現在「さくらのメールボックス」を契約している。

Cloudflare には info@example.comwebmaster@example.com などのカスタムメールアドレスを作成して、任意のメールアドレスへ転送する機能がなんと無料で使える。

左ペインの[メールアドレス]から[Email Routing]を選択する。概要の隣に[ルート]タブがあるのでクリックする。

[アドレスを作成]ボタンをクリックする。

追加したいカスタムメールアドレスを入力する。ここでは info を作成して既存の gmailアカウントに転送するようにした。最後に [保存]ボタンをクリックする。

あとはアクティブになっているのを確認して完了である。

関連記事

ドメイン管理を AWS Route 53 から Cloudflare に移管した

2022年11月に AWS Route 53 のドメイン更新料が値上げされた。一時期と比較してマシになったとは言えるものの円安傾向にある今日日 $1 の値上げでもドメイン数があると結構厳しいなと思っていた。

先日「Route 53 から Cloudflare にドメイン移管したい」という投稿をみて、「そういえば Cloudflare ってCDNだけじゃなくてドメインも売り出したんだっけ……」と調べてみたところ、本記事の執筆時点のレートで Route 53 を契約し続けるよりも約 700円の差があることがわかった

  • Route 53 では comドメインが $13 / year (執筆当時:1,729円/年)
  • Cloudflare では comドメインが $8 / year (執筆当時:1,064円/年)

さらに Route 53 ではホストゾーンごとに年600円の管理費と S3 の転送料がかかる。S3の料金は微々たるものではあるが、Cloudflareへ移管すると 1ドメインあたり年間1,200円近くの節約ができるようだ。ドメイン管理を AWS から Cloudflare に移管することを決意した。

ネームサーバーを Cloudflare に割り当てる

[サイトを追加]ボタンをクリックする。

移管したいドメイン名を入力する。

どのプランを利用するか選択する。無料で管理したいのでここでは[Free]を選択する。

さくさんの場合、ほぼすべてのサブドメインの末端サーバーははてなブログなので、攻撃を受けたとしてもはてな社のプロに任せることができる。どこかのタイミングで自前のWebサーバーを立ち上げたときにはプランを変更するだけでDDoS攻撃から守ってもらえるのは心強い。

入力したドメインから DNS レコードが読み込まれる。問題がなければそのまま [続行]ボタンをクリックする。

はてなブログを独自ドメインを使いたい場合にはプロキシステータスを OFF にしないといけないので注意すること *1

ネームサーバーを変更しろと表示されるので、AWS Route 53 に移動して指定された通りに変更する。

Route 53 でネームサーバーを Cloudflare に切り替える

AWS Route 53へ移動して、移管したいドメインのプロパティからネームサーバーの編集する。ネームサーバー欄の[ネームサーバーの追加/編集]ボタンをクリックすると変更ができる。

後述するが移管のロックを無効化してから実際に移管できるようになるまで2時間ほどかかるため、このタイミングで移管のロックを無効にしておくのも忘れないようにしよう。

ネームサーバーの編集が完了したら Cloudflare に戻る。

Cloudflare に戻って完了ボタンをクリックする

Cloudflare 側の[ネームサーバーをチェックしてください]ボタンをクリックする。

30分ほど待機したのち、Cloudflare のダッシュボード上でDNS管理が Cloudflare に切り替わった旨が表示される。Cloudflare のDNSに切り替え後にドメイン移管ができるようになる。

2時間ほど待機する

ネームサーバーの設定が浸透するのは伝搬されるのは一瞬で終わる。ただし、Cloudflareへドメインを移管できるようになるのは、Route 53の移管のロックを無効してから約1時間半後であった。

次の作業ができるようになるまで時間が空いてしまうので、このタイミングで諸々の確認をしておくこと。

  • DNS の設定が正しいのか? Route 53の既存の設定と比較して漏れがないのか?
  • ページルールを利用してexample.com/* から https://www.example.com へリダイレクトできるようになっているか?
  • Cloudflare のメールアドレス転送サービスを使って info@example.comwebmaster@example.com でメールを受け取れるのか?

ドメインを移管する

「素晴らしいですニュースです!」と表示されていれば、ネームサーバーは移行済みである。ドメイン登録欄の[Cloudflare に移管]ボタンをクリックする。

移管するドメインを選択して[続ける]ボタンをクリックする。.comドメインは安いけど、.netドメインはそれより少し高くなる。ICANN料金は日本語の「移管」と掛けてるのだろうか。

Route 53で承認コードを発行して、Cloudflareで入力する。入力が完了したら[移管を確認して確定する]ボタンをクリックする。

あらかじめメールを転送するように設定しておけば、ドメインレジストラ(この場合はAmazonから)メールが届き、本当に移管するのかどうかのメールが届く。クリックすればすぐに移管が完了するが、メールアドレスの設定をしていなければ5日間の待機期間ののちに移管される。

以上で Route 53から Cloudflare へドメイン管理を移管できた。

トラブルシューティング

さくさんが Cloudflare へドメインを移管するにあたってハマった事例を紹介する。

AWSで移管のロックを無効にしているのに移管ができない

ドメインの移管を進めようとしても、ステータスが「登録状況: クライアントの転送禁止」と表示されて移管処理を進めることができなかった。

英語表記では「Registry status: Client transfer prohibited」となる。対処方法はこのページに書かれている。Route 53側で移管のロックを無効にしなければいけない

ハマりどころとしては、Route 53でロックを無効にしても1時間半から2時間ほど待機しないと、Cloudflare のステータスは変わらず移管可能な状態にならなかった。おそらく Cloudflare側のキャッシュが更新されるのに時間がかかるのだと思う。

楽天VISAカードで支払いに失敗する

Cloudflare への支払い情報として楽天VISAカードを登録していたが、ドメイン移管時に決済が失敗した。楽天VISAカードを登録できるが決済には失敗する謎の現象であった。

支払い方法を PayPal に変更することで問題なく決済が通った。

原因自体はよくわからないが楽天側で不正利用として弾かれているのかもしれない。他の方の記事でも楽天カードがダメだったと書かれていたので自分だけの現象ではないことがわかり PayPal への切り替えができた。

はてなブログで独自ドメインを利用する際にはプロキシを有効にしてはいけない

はてなブログで独自ドメインを利用する場合プロキシを有効にしてはいけない。

プロキシステータスをOFF (DNSのみ)に設定すること。

まとめ

ドメイン管理を AWS Route 53 から Cloudflare に移管する方法を紹介した。AWS から Cloudflare へ移管することでドメインあたり年間1,200円ほどコスト削減できるようになった。

安かろう悪かろうでなく、Cloudflare はサービスとして後発なのもあってダッシュボードが整理されていて必要な機能にアクセスしやすい。AWS は歴史的経緯があるため機能が多くて各機能間の連携がさくさんには難しいところがあった。もし将来的に Cloudflare の費用が値上げされて AWS と同程度のコストがかかるようになった場合でも我が家では Cloudflare を使っていきたいと思う。

最終的にはすべてのドメインを Cloudflare で管理したいと考えているが、 ch3cooh.jp にはサブドメインがたくさんあり整理しないといけないので、現時点での移行を躊躇している。

参考記事

blog.k-bushi.com

zenn.dev