Bash と Caddy サーバで ActivityPub のインスタンス(6) 他のインスタンスのアクターにリプライを送る

## やること
他のインスタンスのアクターにリプライを送る。

## ActivityPub でリプライを送るとは
詳しくは [Fediverse入門―非中央集権型SNSサーバを作ろう！](https://gihyo.jp/dev/serial/01/perl-hackers-hub/005901)をご一読を。簡単に要約すると
- 自分の投稿したい文章を json 化したオブジェクトを作り
- それを type が Create の json オブジェクトに包んで
- リプライを送る相手とフォロワーの inbox に叩きつける

である。ただしここでは公開範囲は考えていない。
なので初めに Note オブジェクトを作ってから、 Create オブジェクトにするのが正道だと思うが、最初から Create にした json を作ってその各要素を置換する、という方法を取る。何故なら面倒だから。そして各フォロワーにも配送するのは後に回す。どうせまだいないはずだし。

```json:reply.json
{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://{my_instance}/users/{myself}/statuses/{id}/activity",
  "type": "Create",
  "actor": "https://{my_instance}/users/{myself}",
  "published": "{date_now}",
  "to": [
    "https://www.w3.org/ns/activitystreams#Public"
  ],
  "cc": [
    "https://{my_instance}/users/{myself}/followers",
    "{URL}"
  ],
  "object": {
    "id": "https://{my_instance}/users/{myself}/statuses/{id}",
    "type": "Note",
    "inReplyTo": {id_of_reply_to},
    "published": "{date_now}",
    "attributedTo": "https://{my_instance}/users/{myself}",
    "to": [
      "https://www.w3.org/ns/activitystreams#Public"
    ],
    "cc": [
      "https://{my_instance}/users/{myself}/followers",
      "{URL}"
    ],
    "content": "<p><span class=\"h-card\" translate=\"no\"><a href=\"{URL}\" class=\"u-{URL} mention\">@<span>{to}</span></a></span> {content}</p>",
    "tag": [
      {
        "type": "Mention",
        "href": "{URL}",
        "name": "@{to}@{to_instance}"
      }
    ]
  }
}
```
この json の波括弧付きの部分を適切なものに置換し、curl で POST する。{myself} などは like と変わらない。その他
{date_now} => Note Create の作成時刻
{to} => リプライを送る相手のアクター名
{to_instance} => 相手のインスタンス
{URL} => 送る相手のプロフィールページの URL
{content} => 送る文章
に置換して送りつける json に変える。
"inReplyTo": {id_of_reply_to}
は何かというと、ただ相手にメンションするだけならこのタグには null (ダブルクォーテーションなし)でいいが、相手の Note に対してのリプライならばその Note の ID を入れるタグである。


## スクリプトを作って実行、その他
前回の post_like.sh を少々変えて reply.sh を作る。

```bash:reply.sh
#!/bin/bash


# 自分のインスタンスとアクター名を @アクター名@インスタンス名 で入力
read -p "@myself@my_instance: " MY
MY_arry=(${MY//@/ }) # @ で MY を分割し、配列に入れる
myself=${MY_arry[0]}
my_instance=${MY_arry[1]}
echo

# reply したい相手のインスタンスとアクター名を @アクター名@インスタンス名 で入力
read -p "@to@to_instance: " TO
TO_arry=(${TO//@/ })
to=${TO_arry[0]}
to_instance=${TO_arry[1]}
echo

# reply したい投稿の ID を入力
read -p "which id do you wanna reply?: " id_of_reply_to
echo

if [ -n "$id_of_reply_to" ]; then
	id_of_reply_to=\"$id_of_reply_to\" # id_of_reply_to が空でなければ DQ を付ける
else id_of_reply_to="null"
fi

# reply の内容
read -p "what do you wanna mention?: " content
echo


unixtime=$(date "+%s")
date_now=$(date -uR  | head -c -6 | sed -e 's/$/GMT/')

# 相手のインスタンス・アクター名から URL と inbox を取得。また " を外しておく
URL=$(curl https://$to_instance/.well-known/webfinger?resource=acct:$to@$to_instance | jq -r '.links[] | select (.rel == "self")'.href | tr -d "\"")
echo $URL # 4debug
INBOX=$(curl -H "Accept: application/activity+json" $URL | jq .inbox | tr -d "\"")
# request-target は https://インスタンス 以下である(よって最初に "/" を付ける)
REQUEST_TARGET=$(echo  "/"$(sed -E 's/^.*(https):\/\/([^/]+).([^/]+)/\3/g' <<< $INBOX))

sed -e "s/{id}/$unixtime/g" reply.json > live
sed -i "s/{myself}/$myself/g" live
sed -i "s/{my_instance}/$my_instance/g" live

sed -i "s/{to}/$to/g" live
sed -i "s/{to_instance}/$to_instance/g" live
sed -i "s/{date_now}/$date_now/g" live
sed -i "s|{id_of_reply_to}|$id_of_reply_to|g" live # / が入ってるので区切り文字を変える
sed -i "s|{URL}|"$URL"|g" live # 同上
sed -i "s/{content}/$content/g" live # この方法だと content に / が入っていたらバグる


body=$(cat live | jq -c .)
digest_val=$(echo -n "$body" | sha256sum | xxd -r -p | base64)

signature_headers="(request-target): post ${REQUEST_TARGET}\ndate: ${date_now}\nhost: ${to_instance}\ndigest: SHA-256=${digest_val}"
echo -e "$signature_headers" > 4_sig_headers # 後に見返すためにファイルに
sig_val=$(echo -en $signature_headers | openssl dgst -binary -sign ./personal/private -sha256 | openssl enc -A -base64)
signature="Signature: keyId=\"https://${my_instance}/users/${myself}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) date host digest\",signature=\"$sig_val\""


curl -H "Date: $date_now" -H "Digest: SHA-256=$digest_val" -H "$signature" $INBOX -d "$body"

echo $body | jq .object > ./statuses/$unixtime
echo $body > ./post/$unixtime
```

これでメンション・リプライが送れるが、後のことを考えて Note オブジェクトを取っておくことにした。
なので送る Create の object タグの内容を statuses というディレクトリに保存する。
また、送る json その物も post ディレクトリに保持することにする。
```
mkdir statuses post
```
そして reply.sh の最後に
```
echo $body | jq .object > ./statuses/$unixtime
cho $body > ./post/$unixtime
```
を付け加えた。
また、コード内にコメントしたようにリプライ内容に "/" (スラッシュ)が入っていると sed の区切り文字と重複して sed が上手く作動しないので、その場合は "\\/" とエスケープする。


## 参考記事
[Fediverse入門―非中央集権型SNSサーバを作ろう！](https://gihyo.jp/dev/serial/01/perl-hackers-hub/005901)