最新のスクリプト:https://gist.github.com/eallion/bf8861eb3292c2351c1067fba3198c26
更新:宝塔パネルの例を追加しました。
TL;DR#
このページに直接ジャンプして スクリプト内容 を確認してください。
前言#
運が良いのか悪いのか分かりませんが、Memos を主力ツールとして私のワークフローに加えようとしているときに、v0.19.0
のバージョン更新に遭遇しました。このバージョン更新は一連の大きな問題をもたらしました。Memos の新バージョンの堅牢性が疑問視されているだけでなく、それを搭載したサーバー自体も影響を受け、64G メモリの物理マシンでも動作しませんでした。ネットユーザーが言うように、Memos はただの練習用プロジェクトのようです。思い切って捨てました。Google Keep や Obsidian などのツールはどこが悪いのでしょうか?すべてのタスクを一つのツールに束縛しないのは確かに少し面倒ですが、All in one は基本的に All in boom でもあります。
今、私は Memos を私の Mastodon のバックアップ用として位置付けています(その一つの方法です)。
私は Webhook のような能動的なプッシュ方式が好きです。RSS や Cron のような受動的なプル方式よりもシンプルで、低炭素で環境に優しく、即時性があります。主に能動的な感覚を掌握することがとても爽快です。
ここからは、Mastodon を利用して Webhook で Memos にツイートを同期する方法を紹介します。私は Shell Script を使用しており、非常にシンプルなスクリプトで、いくつかの常識的な論理判断を行っていますが、完璧ではないかもしれません。Node.js や Python などでも実現可能です。
テスト済みバージョン#
Mastodon には自分のインスタンスが必要で、または Webhook を作成できる管理者権限を持つアカウントが必要です。
ツールのインストール#
サーバーにツールをインストールしてください。エラーが発生した場合は、エラーログに基づいて他の対応するツールをインストールしてください。
sudo apt install jq
sudo apt install lynx
Webhook ツール#
- https://github.com/jkjoy/docker-webhook
- https://github.com/adnanh/webhook
- 宝塔パネル - ソフトウェアストア - Webhook を検索してインストール
- 1Panel - 不明
Mastodon に移動して https://{INSTANCE}/admin/webhooks
で Webhook を作成します。
イベントは status:created
のみを選択できます。返信やリブログもこのイベントに含まれます。
目的地 URL には自分がデプロイした Webhook のリンクを入力します。例:https://webhook.example.com/hooks/mastodon-sync-to-memos
宝塔のものは:https://webhook.mybtserver.com:8888/hook?access_key=ACCESSKEY
Mastodon の Webhook 目的地 URL はドメインにバインドすることをお勧めします。さもなければ、Sidekiq が処理できない可能性があります。
スクリプト内容#
以下のスクリプト内容をサーバー上の .sh
ファイルに保存します。例えば、現在のユーザーのホームディレクトリ(~
)の ~/mastodon_sync_to_memos.sh
ファイルに保存し、以下の内容を設定してください。置き換えに注意してください:
MEMOS_HOST=""
MEMOS_ACCESS_TOKEN=""
MEMOS_VISIBILITY=""
MASTODON_INSTANCE=""
MASTODON_ID=""
SKIP_MASTODON_REPLY=
SKIP_MASTODON_REBLOG=
HOME_DIR=~
FILE_PATH=$HOME_DIR/.mastodon_memos_id.json
Mastodon ID を探す: https://
INSTANCE
/api/v1/accounts/lookup?acct=USERNAME
#!/bin/bash
# テスト済みバージョン:
# Memos: v0.18.2
# Mastodon: v4.2.8
# ======================================================
# 設定開始
# Memos ホスト
MEMOS_HOST=""
# Memos アクセストークン
MEMOS_ACCESS_TOKEN=""
# Memos の可視性 ('PUBLIC', 'PROTECTED', 'PRIVATE') のいずれかを選択
MEMOS_VISIBILITY=PUBLIC
# Mastodon インスタンス
MASTODON_INSTANCE=""
# Mastodon ID, ID を見つける: https://INSTANCE/api/v1/accounts/lookup?acct=USERNAME
MASTODON_ID=""
# 返信とリブログをスキップ
SKIP_MASTODON_REPLY=true
SKIP_MASTODON_REBLOG=true
# 現在のユーザーのホームディレクトリのパスと ID を保存するファイルを取得し、デフォルトを保持し、変更しないでください
HOME_DIR=~
FILE_PATH=$HOME_DIR/.mastodon_memos_id.json
# 設定終了
# ======================================================
# 以下の内容は変更しないでください
# ID ファイルが存在するか確認
if [ ! -f "$FILE_PATH" ]; then
# ファイルが存在しない場合、ファイルを作成し JSON データを書き込みます
echo '
{
"latest_memos_id": "0",
"latest_mastodon_id": "0",
"bind": []
}
' > "$FILE_PATH"
echo "データファイルが作成されました: $FILE_PATH"
else
# ファイルが存在する場合、スキップして次のステップに進みます
echo "ローカルデータが存在するため、スキップします..."
fi
# API とトークンを結合
if [[ "$MEMOS_HOST" != */ ]]; then
MEMOS_HOST="$MEMOS_HOST/"
fi
MEMOS_API_HOST="${MEMOS_HOST}api/v1/memo"
AUTHORIZATION="Bearer ${MEMOS_ACCESS_TOKEN}"
# Memos の最新の Memos ID を取得
MEMOS_URL="${MEMOS_API_HOST}?creatorId=101&rowStatus=NORMAL&limit=1"
LATEST_MEMOS_ID=$(curl --connect-timeout 60 -s $MEMOS_URL | jq -r '.[0].id')
# Mastodon の API
if [[ "$MASTODON_INSTANCE" != */ ]]; then
MASTODON_INSTANCE="$MASTODON_INSTANCE/"
fi
CONTENT_URL="${MASTODON_INSTANCE}api/v1/accounts/${MASTODON_ID}/statuses?limit=1&exclude_replies=${SKIP_MASTODON_REPLY}&exclude_reblogs=${SKIP_MASTODON_REBLOG}"
# Mastodon の最新ステータスの ID
LATEST_MASTODON_ID=$(curl --connect-timeout 60 -s $CONTENT_URL | jq -r '.[0].id')
# LOCAL_MEMOS_ID 変数を定義
LOCAL_MEMOS_ID=$(cat "$FILE_PATH" | jq -r '.latest_memos_id')
LOCAL_MASTODON_ID=$(cat "$FILE_PATH" | jq -r '.latest_mastodon_id')
# Webhook がトリガーされたとき、Mastodon の最新 ID がローカル ID と同じかどうかを判断し、重複同期を防ぎます
if [ "$LATEST_MASTODON_ID" == "$LOCAL_MASTODON_ID" ]; then
echo "Mastodon は更新されていません、スキップします..."
echo "スキップされた: $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")"
echo "============================="
exit 0
fi
CONTENT=$(curl --connect-timeout 60 -s $CONTENT_URL | jq -r '.[0]')
MEDIA=$(echo $CONTENT | jq -r '.media_attachments')
# Media の内容を判断
if [ "$MEDIA" != "null" ]; then
MEDIAS=$(echo $CONTENT | jq -r '.media_attachments[] | select(.type=="image") | .url')
# 画像を結合
images=""
for url in $MEDIAS; do
images="$images![image]($url)\n"
done
TEXT=$(echo "$CONTENT" | jq -r '.content' | lynx -dump -stdin -nonumbers -nolist | tr -d '\n' | sed '/^$/N;s/\n\n/\n/g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed -E 's/ {2,}/ /g')
TEXT="$TEXT\n$images"
else
# 通常の内容
TEXT=$(echo "$CONTENT" | jq -r '.content' | lynx -dump -stdin -nonumbers -nolist | tr -d '\n' | sed '/^$/N;s/\n\n/\n/g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed -E 's/ {2,}/ /g')
fi
# 内容が空かどうかを判断
if [ -z "$TEXT" ] || [ "$TEXT" == "\\n" ]; then
echo "内容が空です、スキップします..."
echo "スキップされた: $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")"
echo "============================="
exit 0
fi
# ダブルクオートをエスケープ
TEXT=$(echo "$TEXT" | sed 's/"/\\"/g')
# Webhook がトリガーされたとき、Memos の最新 ID がローカル ID と同じかどうかを判断
# Memos が一方的に更新された後、Mastodon と Memos の ID のバインディング関係を検証します(Todo)
#if [ "$LATEST_MEMOS_ID" == "$LOCAL_MEMOS_ID" ]; then
# echo "Memos は更新されていません、スキップします..."
# echo "スキップされた: $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")"
# echo "============================="
# exit 0
#fi
# Mastodon と Memos の Content 内容の MD5 値を比較します(必ずしも正確ではありません)
# 後で GPT を導入して内容を比較することを試みます
CONTENT_MEMOS=$(curl --connect-timeout 60 -s $MEMOS_URL | jq '.[0].content')
CONTENT_MASTODON=$TEXT
# 最新の Memos の MD5 を取得
LATEST_MEMOS_MD5=$(echo $CONTENT_MEMOS | tr -d '"' | md5sum | cut -d' ' -f1)
# 最新の Mastodon の MD5 を取得
LATEST_TEXT_MD5=$(echo $TEXT | tr -d '"' | md5sum | cut -d' ' -f1)
# MD5 を通じて内容が重複しているかどうかを判断
if [ "$LATEST_TEXT_MD5" == "$LATEST_MEMOS_MD5" ]; then
echo "内容が重複しています、スキップします..."
echo "スキップされた: $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")"
echo "============================="
exit 0
fi
# NeoDB の評価 Emoji を置き換え
TEXT=$(echo "$TEXT" | sed "s/:star_empty:/🌑/g; s/:star_half:/🌗/g; s/:star_solid:/🌕/g")
# 最後の空行を削除
TEXT=$(echo "$TEXT" | sed 's/\\n$//')
# Memos に公開し、返された JSON データを取得
RESPONSE=$(curl -s -X POST \
-H "Accept: application/json" \
-H "Authorization: $AUTHORIZATION" \
-d "{ \"content\": \"$TEXT\", \"visibility\": \"$MEMOS_VISIBILITY\"}" \
$MEMOS_API_HOST)
# 返された JSON データから Memos の id 値を抽出
NEW_MEMOS_ID=$(echo "$RESPONSE" | jq -r '.id')
# JSON ファイル内の latest_memos_id の値を更新
jq ".latest_memos_id = \"$NEW_MEMOS_ID\"" "$FILE_PATH" > "${FILE_PATH}.tmp" && mv "${FILE_PATH}.tmp" "$FILE_PATH"
# JSON ファイル内の latest_mastodon_id の値を更新
jq ".latest_mastodon_id = \"$LATEST_MASTODON_ID\"" "$FILE_PATH" > "${FILE_PATH}.tmp" && mv "${FILE_PATH}.tmp" "$FILE_PATH"
# Mastodon と Memos の ID のバインディング関係を更新し、"bind" 内の配列が一意のキーを保持し、キーも一意の値のみを持つことを確認します
jq ".bind += [{\"$LATEST_MASTODON_ID\": \"$NEW_MEMOS_ID\"}] | .bind = (.bind | unique)" "$FILE_PATH" > "${FILE_PATH}.tmp" && mv "${FILE_PATH}.tmp" "$FILE_PATH"
echo "Mastodon を Memos に同期しました!"
echo "完了: $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")"
echo "============================="
宝塔パネル#
宝塔パネルで Webhook プラグインを使用する場合、上記のスクリプト内容を Webhook プラグインのスクリプトに直接コピーできます。サーバーで手動で .sh
ファイルを作成する必要はありません。
JSON データファイルの内容#
スクリプトを初めて実行すると、現在のユーザーのホームディレクトリ ~
に新しいファイル ~/.mastodon_memos_id.json
が作成され、初期化されます。以降、このファイルは Mastodon ID と Memos ID のバインディング関係を記録します。ホームディレクトリに作成したくない場合は、HOME_DIR=
と FILE_PATH=
の 2 つのパラメータを変更する必要があります。
{
"latest_memos_id": "",
"latest_mastodon_id": "",
"bind": []
}
本番環境で生成されたデータの例:
{
"latest_memos_id": "6231",
"latest_mastodon_id": "112061852482921394",
"bind": [
{
"112059053750743781": "6230"
},
{
"112061852482921394": "6231"
}
]
}