Skip to content

Instantly share code, notes, and snippets.

@eallion
Last active December 16, 2024 16:24
Show Gist options
  • Save eallion/bf8861eb3292c2351c1067fba3198c26 to your computer and use it in GitHub Desktop.
Save eallion/bf8861eb3292c2351c1067fba3198c26 to your computer and use it in GitHub Desktop.
Mastodon 同步到 Memos 脚本

Mastodon 同步到 Memos 脚本

利用 Mastodon 的 Webhook 可以主动同步 Status 到 Memos(或其他平台),而不是利用 RSS 或 Crontab 被动式同步。

已测试版本

安装工具

请安装工具,若有报错,请安装其他对应工具

  • sudo apt install jq
  • sudo apt install lynx

配置

以下为 Shell Script 脚本内容,请注意替换:

  • 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
  • AI_DIFF=true
  • AI_API="https://api.deepseek.com"
  • AI_AUTHORIZATION=""
  • AI_MODEL
  • SINK_ENABLE
  • SINK_HOST="https://s.e5n.cc"
  • SINK_NUXT_SITE_TOKEN=""
  • S3_ENABLE

查找 ID: https://INSTANCE/api/v1/accounts/lookup?acct=USERNAME

JSON 数据文件内容

.mastodon_memos_id.json

{
  "latest_memos_id": "",
  "latest_mastodon_id": "",
  "bind": []
}

脚本内容

mastodon_sync_to_memos.sh

#!/bin/bash

sleep 5

# Version: 2024.11.23

# 已测试版本:
# Memos: v0.22.5
# Mastodon: v4.3.1
# Sink: v0.1.4

# ======================================================
# 配置开始

# Memos Host
MEMOS_HOST="https://memos.eallion.com/"

# Memos Access Token
MEMOS_ACCESS_TOKEN="eyJh****"

# 发布 Memos 的可见性 ('PUBLIC', 'PROTECTED', 'PRIVATE', 'VISIBILITY_UNSPECIFIED') 四选一
MEMOS_VISIBILITY=PUBLIC

# Mastodon Instance
MASTODON_INSTANCE="https://e5n.cc/"

# Mastodon ID, Find ID: https://INSTANCE/api/v1/accounts/lookup?acct=USERNAME
MASTODON_ID="111136231674527355"

# 跳过回复和转嘟
SKIP_MASTODON_REPLY=true
SKIP_MASTODON_REBLOG=true

# 获取当前用户的家目录路径及保存 ID 的文件,保持默认,不用更改
HOME_DIR=~
FILE_PATH=$HOME_DIR/.mastodon_memos_id.json

# AI 比较文本相似度,兼容 OpenAI 格式的模型都可以
AI_DIFF=false
AI_API="https://api.deepseek.com"
AI_TOKEN="sk-****"
AI_MODEL="deepseek-chat"

# Deploy Sink: https://github.com/ccbikai/Sink
SINK_ENABLE=false
SINK_HOST="https://s.e5n.cc"
SINK_NUXT_SITE_TOKEN="SINK-****"

# 上传 Statuses 到 s3 对象存储
# 未配置好最下面的 aliyun cli 或者 coscmd,请勿打开此设置
S3_ENABLE=false

# 配置结束
# ======================================================

# 以下内容不用更改

# 检查 ID 文件是否存在
if [ ! -f "$FILE_PATH" ]; then
  # 如果文件不存在,则创建文件并写入 JSON 数据
  echo '
{
  "latest_memos_id": "0",
  "latest_mastodon_id": "0",
  "bind": []
}
' > "$FILE_PATH"
  echo "Data file created: $FILE_PATH"
else
  # 如果文件存在,则跳过并进行后续步骤
  echo "Local data exist, skipping..."
fi

# 拼接 Memos API 和 Token
if [[ "$MEMOS_HOST" != */ ]]; then
  MEMOS_HOST="$MEMOS_HOST/"
fi
MEMOS_API_HOST="${MEMOS_HOST}api/v1/memos"

MEMOS_URL="${MEMOS_API_HOST}?pageSize=1&filter=creator%3D%3D%27users%2F101%27%26%26visibilities%3D%3D%5B%27PUBLIC%27%5D"

# Mastodon 的 API
if [[ "$MASTODON_INSTANCE" != */ ]]; then
  MASTODON_INSTANCE="$MASTODON_INSTANCE/"
fi
MASTODON_CONTENT_URL="${MASTODON_INSTANCE}api/v1/accounts/${MASTODON_ID}/statuses?limit=1&exclude_replies=${SKIP_MASTODON_REPLY}&exclude_reblogs=${SKIP_MASTODON_REBLOG}"

# 前置判断是否为回复嘟文,减少 AI Token 开支
LATEST_CONTENT_URL="${MASTODON_INSTANCE}api/v1/accounts/${MASTODON_ID}/statuses?limit=1"
LATEST_CONTENT_RESPONSE=$(curl -s "$LATEST_CONTENT_URL")
IS_REPLY=$(echo "$LATEST_CONTENT_RESPONSE" | jq -r '.[0].in_reply_to_id')
IS_REBLOG=$(echo "$LATEST_CONTENT_RESPONSE" | jq -r '.[0].reblog')
# 检查 IS_REPLY 是否为 null
if [ "$SKIP_MASTODON_REPLY" == true ] && [ "$IS_REPLY" != "null" ]; then
  echo "Latest status is reply, exiting..."
  echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
  echo "============================="
  exit 0
fi
# 前置判断是否为转载,减少 AI Token 开支
if [ "$SKIP_MASTODON_REBLOG" == true ] && [ "$IS_REBLOG" != "null" ]; then
  echo "Latest status is reblog, exiting..."
  echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
  echo "============================="
  exit 0
fi

# Mastodon 最新 Status 的 ID
LATEST_MASTODON_ID=$(curl --connect-timeout 60 -s $MASTODON_CONTENT_URL | jq -r '.[0].id')

# Memos 获取最新的 Memos ID
LATEST_MEMOS_ID=$(curl --connect-timeout 60 -s $MEMOS_URL | jq -r '.memos[0].uid')

# 定义 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 no updated, skipping..."
  echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
  echo "============================="
  exit 0
fi

CONTENT=$(curl --connect-timeout 60 -s $MASTODON_CONTENT_URL | jq -r '.[0]')

TEXT=$(echo "$CONTENT" | jq -r '.content')

# 解码 Unicode 转义序列
TEXT=$(echo "$TEXT" | sed 's/\\u[0-9a-fA-F]\{4\}/\\x/g')

# 去除外层引号
TEXT=$(echo "$TEXT" | sed 's/^"//;s/"$//')

# 处理 <span> 标签(去除)
TEXT=$(echo "$TEXT" | sed -E 's/<span[^>]*>//g;s/<\/span>//g')

# 处理 <a> 标签,根据 class 属性进行不同的处理
# 如果 class 包含 hashtag,保留标签内的文字内容
TEXT=$(echo "$TEXT" | sed -E 's/<a[^>]*class="[^"]*hashtag[^"]*"[^>]*>([^<]*)<\/a>/\1/g')
# 如果 class 不包含 hashtag,提取 href 内容
TEXT=$(echo "$TEXT" | sed -E 's/<a[^>]*href="([^"]+)"[^>]*>([^<]*)<\/a>/\1/g')

# 去除 <p> 标签
TEXT=$(echo "$TEXT" | sed 's/<p[^>]*>//g;s/<\/p>//g')

# 处理 <br /> 标签,替换为换行符
TEXT=$(echo "$TEXT" | sed 's/<br \/>/\n/g')

# 处理 blockquote,替换为 Markdown 的 `>`
TEXT=$(echo "$TEXT" | sed 's/<blockquote>/>/g;s/<\/blockquote>//g')

# 替换 &gt;
TEXT=$(echo "$TEXT" | sed 's/\&gt\;/>/g')

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 "$TEXT\n$images" | sed 's/\\n*$//')
else
  # 普通内容
  TEXT=$(echo "$TEXT" | sed 's/\\n*$//')
fi

# 判断内容是否为空
if [ -z "$TEXT" ] || [ "$TEXT" == "\\n" ]; then
  echo "Content is empty, skipping..."
  echo "Skipped: $(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 no updated, skipping..."
#  echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
#  echo "============================="
# exit 0
# fi

# 利用 Deepseek 对比 Mastodon 和 Memos 的相似度
CONTENT_MEMOS=$(curl --connect-timeout 60 -s $MEMOS_URL | jq -r '.memos[0].content')
CONTENT_MASTODON=$TEXT

if [[ "$AI_DIFF" == true ]]; then
    REQUEST_BODY=$(cat <<EOF
        {
          "messages": [
            {
              "content": "你是一个比较文本相似度的助手",
              "role": "system"
            },
            {
              "content": "比较文本1:“ --- $CONTENT_MEMOS --- ”和文本2:“ --- $CONTENT_MASTODON --- ”的相似度,超过65%的相似度就判定为相似,如果相似就回答数字1,如果不相似就回答数字0,除了数字1或者数字0不能回答其他任何内容。",
              "role": "user"
            }
          ],
          "model": "$AI_MODEL",
          "frequency_penalty": 0,
          "max_tokens": 2048,
          "presence_penalty": 0,
          "stop": null,
          "stream": false,
          "temperature": 1,
          "top_p": 1,
          "logprobs": false,
          "top_logprobs": null
        }
EOF
    )

    AI_RESPONSE=$(curl -s -L -X POST "$AI_API/chat/completions" \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H "Authorization: Bearer $AI_TOKEN" \
    --data-raw "$REQUEST_BODY")

    AI_DIFF_RESULT=$(echo "$AI_RESPONSE" | jq -r '.choices[0].message.content')
    if [ "$AI_DIFF_RESULT" == 1 ]; then
      echo "[AI] Content is duplicate, skipping..."
      echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
      echo "============================="
      exit 0
    fi
else
    # 对比 Matodon 和 Memos 的 Content 内容的 MD5 值(不一定精确)

    # 获取最新 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 "[MD5] Content is duplicate, skipping..."
      echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
      echo "============================="
      exit 0
    fi
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 数据
MEMOS_RESPONSE=$(curl --request POST \
  --url $MEMOS_API_HOST \
  --header "Authorization: Bearer $MEMOS_ACCESS_TOKEN" \
  --data "{
  \"content\": \"$TEXT\",
  \"visibility\": \"$MEMOS_VISIBILITY\"
}")

# 从返回的 JSON 数据中提取 Memos 的 id 值
NEW_MEMOS_ID=$(echo "$MEMOS_RESPONSE" | jq -r '.uid')

# 更新 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"

# POST 到 Sink
if [[ "$SINK_ENABLE" == true ]]; then
  SINK_URL="${MASTODON_INSTANCE}@eallion/${LATEST_MASTODON_ID}"
  SINK_SLUG="${NEW_MEMOS_ID}"

  curl -s -X POST \
      -H "authorization: Bearer ${SINK_NUXT_SITE_TOKEN}" \
      -H "content-type: application/json" \
      -d "{\"url\": \"${SINK_URL}\", \"slug\": \"${SINK_SLUG}\"}" \
      "${SINK_HOST}/api/link/create"
fi

# 上传 Statuses 到 s3
if [[ "$S3_ENABLE" == true ]]; then
  URL_E5N="${MASTODON_INSTANCE}api/v1/accounts/${MASTODON_ID}/statuses?limit=20&exclude_replies=${SKIP_MASTODON_REPLY}&exclude_reblogs=${SKIP_MASTODON_REBLOG}"
  URL_EMOJI="${MASTODON_INSTANCE}api/v1/custom_emojis"
  OUTPUT_E5N="statuses"
  OUTPUT_EMOJI="custom_emojis"

  curl -s "$URL_E5N" > "$OUTPUT_E5N" || { echo "Error fetching e5n data"; exit 1; }
  curl -s "$URL_EMOJI" > "$OUTPUT_EMOJI" || { echo "Error fetching e5n data"; exit 1; }

  # coscmd upload statuses api/v1/accounts/111136231674527355/ -f -H "{'Content-Type':'application/json'}"
  # coscmd upload custom_emojis api/v1/ -f -H "{'Content-Type':'application/json'}"
  # tccli teo CreatePurgeTask --cli-unfold-argument --ZoneId zone-2ssxnpo34mu5 --Type purge_host --Method delete --Targets 'mastodon.api.eallion.com'

  # aliyun oss cp statuses oss://eallion-com/api/v1/accounts/111136231674527355/ --meta Content-Type:application/json -f
  # aliyun oss cp custom_emojis oss://eallion-com/api/v1/ --meta Content-Type:application/json -f

  rm statuses
  rm custom_emojis
fi

echo "Sync Mastodon to Memos Successful!"
echo "Done: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
echo "============================="
{
"latest_memos_id": "",
"latest_mastodon_id": "",
"bind": []
}
#!/bin/bash
sleep 5
# Version: 2024.11.23
# 已测试版本:
# Memos: v0.22.5
# Mastodon: v4.3.1
# Sink: v0.1.4
# ======================================================
# 配置开始
# Memos Host
MEMOS_HOST="https://memos.eallion.com/"
# Memos Access Token
MEMOS_ACCESS_TOKEN="eyJh****"
# 发布 Memos 的可见性 ('PUBLIC', 'PROTECTED', 'PRIVATE', 'VISIBILITY_UNSPECIFIED') 四选一
MEMOS_VISIBILITY=PUBLIC
# Mastodon Instance
MASTODON_INSTANCE="https://e5n.cc/"
# Mastodon ID, Find ID: https://INSTANCE/api/v1/accounts/lookup?acct=USERNAME
MASTODON_ID="111136231674527355"
# 跳过回复和转嘟
SKIP_MASTODON_REPLY=true
SKIP_MASTODON_REBLOG=true
# 获取当前用户的家目录路径及保存 ID 的文件,保持默认,不用更改
HOME_DIR=~
FILE_PATH=$HOME_DIR/.mastodon_memos_id.json
# AI 比较文本相似度,兼容 OpenAI 格式的模型都可以
AI_DIFF=false
AI_API="https://api.deepseek.com"
AI_TOKEN="sk-****"
AI_MODEL="deepseek-chat"
# Deploy Sink: https://github.com/ccbikai/Sink
SINK_ENABLE=false
SINK_HOST="https://s.e5n.cc"
SINK_NUXT_SITE_TOKEN="SINK-****"
# 上传 Statuses 到 s3 对象存储
# 未配置好最下面的 aliyun cli 或者 coscmd,请勿打开此设置
S3_ENABLE=false
# 配置结束
# ======================================================
# 以下内容不用更改
# 检查 ID 文件是否存在
if [ ! -f "$FILE_PATH" ]; then
# 如果文件不存在,则创建文件并写入 JSON 数据
echo '
{
"latest_memos_id": "0",
"latest_mastodon_id": "0",
"bind": []
}
' > "$FILE_PATH"
echo "Data file created: $FILE_PATH"
else
# 如果文件存在,则跳过并进行后续步骤
echo "Local data exist, skipping..."
fi
# 拼接 Memos API 和 Token
if [[ "$MEMOS_HOST" != */ ]]; then
MEMOS_HOST="$MEMOS_HOST/"
fi
MEMOS_API_HOST="${MEMOS_HOST}api/v1/memos"
MEMOS_URL="${MEMOS_API_HOST}?pageSize=1&filter=creator%3D%3D%27users%2F101%27%26%26visibilities%3D%3D%5B%27PUBLIC%27%5D"
# Mastodon 的 API
if [[ "$MASTODON_INSTANCE" != */ ]]; then
MASTODON_INSTANCE="$MASTODON_INSTANCE/"
fi
MASTODON_CONTENT_URL="${MASTODON_INSTANCE}api/v1/accounts/${MASTODON_ID}/statuses?limit=1&exclude_replies=${SKIP_MASTODON_REPLY}&exclude_reblogs=${SKIP_MASTODON_REBLOG}"
# 前置判断是否为回复嘟文,减少 AI Token 开支
LATEST_CONTENT_URL="${MASTODON_INSTANCE}api/v1/accounts/${MASTODON_ID}/statuses?limit=1"
LATEST_CONTENT_RESPONSE=$(curl -s "$LATEST_CONTENT_URL")
IS_REPLY=$(echo "$LATEST_CONTENT_RESPONSE" | jq -r '.[0].in_reply_to_id')
IS_REBLOG=$(echo "$LATEST_CONTENT_RESPONSE" | jq -r '.[0].reblog')
# 检查 IS_REPLY 是否为 null
if [ "$SKIP_MASTODON_REPLY" == true ] && [ "$IS_REPLY" != "null" ]; then
echo "Latest status is reply, exiting..."
echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
echo "============================="
exit 0
fi
# 前置判断是否为转载,减少 AI Token 开支
if [ "$SKIP_MASTODON_REBLOG" == true ] && [ "$IS_REBLOG" != "null" ]; then
echo "Latest status is reblog, exiting..."
echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
echo "============================="
exit 0
fi
# Mastodon 最新 Status 的 ID
LATEST_MASTODON_ID=$(curl --connect-timeout 60 -s $MASTODON_CONTENT_URL | jq -r '.[0].id')
# Memos 获取最新的 Memos ID
LATEST_MEMOS_ID=$(curl --connect-timeout 60 -s $MEMOS_URL | jq -r '.memos[0].uid')
# 定义 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 no updated, skipping..."
echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
echo "============================="
exit 0
fi
CONTENT=$(curl --connect-timeout 60 -s $MASTODON_CONTENT_URL | jq -r '.[0]')
TEXT=$(echo "$CONTENT" | jq -r '.content')
# 解码 Unicode 转义序列
TEXT=$(echo "$TEXT" | sed 's/\\u[0-9a-fA-F]\{4\}/\\x/g')
# 去除外层引号
TEXT=$(echo "$TEXT" | sed 's/^"//;s/"$//')
# 处理 <span> 标签(去除)
TEXT=$(echo "$TEXT" | sed -E 's/<span[^>]*>//g;s/<\/span>//g')
# 处理 <a> 标签,根据 class 属性进行不同的处理
# 如果 class 包含 hashtag,保留标签内的文字内容
TEXT=$(echo "$TEXT" | sed -E 's/<a[^>]*class="[^"]*hashtag[^"]*"[^>]*>([^<]*)<\/a>/\1/g')
# 如果 class 不包含 hashtag,提取 href 内容
TEXT=$(echo "$TEXT" | sed -E 's/<a[^>]*href="([^"]+)"[^>]*>([^<]*)<\/a>/\1/g')
# 去除 <p> 标签
TEXT=$(echo "$TEXT" | sed 's/<p[^>]*>//g;s/<\/p>//g')
# 处理 <br /> 标签,替换为换行符
TEXT=$(echo "$TEXT" | sed 's/<br \/>/\n/g')
# 处理 blockquote,替换为 Markdown 的 `>`
TEXT=$(echo "$TEXT" | sed 's/<blockquote>/>/g;s/<\/blockquote>//g')
# 替换 &gt;
TEXT=$(echo "$TEXT" | sed 's/\&gt\;/>/g')
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 "$TEXT\n$images" | sed 's/\\n*$//')
else
# 普通内容
TEXT=$(echo "$TEXT" | sed 's/\\n*$//')
fi
# 判断内容是否为空
if [ -z "$TEXT" ] || [ "$TEXT" == "\\n" ]; then
echo "Content is empty, skipping..."
echo "Skipped: $(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 no updated, skipping..."
# echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
# echo "============================="
# exit 0
# fi
# 利用 Deepseek 对比 Mastodon 和 Memos 的相似度
CONTENT_MEMOS=$(curl --connect-timeout 60 -s $MEMOS_URL | jq -r '.memos[0].content')
CONTENT_MASTODON=$TEXT
if [[ "$AI_DIFF" == true ]]; then
REQUEST_BODY=$(cat <<EOF
{
"messages": [
{
"content": "你是一个比较文本相似度的助手",
"role": "system"
},
{
"content": "比较文本1:“ --- $CONTENT_MEMOS --- ”和文本2:“ --- $CONTENT_MASTODON --- ”的相似度,超过65%的相似度就判定为相似,如果相似就回答数字1,如果不相似就回答数字0,除了数字1或者数字0不能回答其他任何内容。",
"role": "user"
}
],
"model": "$AI_MODEL",
"frequency_penalty": 0,
"max_tokens": 2048,
"presence_penalty": 0,
"stop": null,
"stream": false,
"temperature": 1,
"top_p": 1,
"logprobs": false,
"top_logprobs": null
}
EOF
)
AI_RESPONSE=$(curl -s -L -X POST "$AI_API/chat/completions" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $AI_TOKEN" \
--data-raw "$REQUEST_BODY")
AI_DIFF_RESULT=$(echo "$AI_RESPONSE" | jq -r '.choices[0].message.content')
if [ "$AI_DIFF_RESULT" == 1 ]; then
echo "[AI] Content is duplicate, skipping..."
echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
echo "============================="
exit 0
fi
else
# 对比 Matodon 和 Memos 的 Content 内容的 MD5 值(不一定精确)
# 获取最新 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 "[MD5] Content is duplicate, skipping..."
echo "Skipped: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
echo "============================="
exit 0
fi
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 数据
MEMOS_RESPONSE=$(curl --request POST \
--url $MEMOS_API_HOST \
--header "Authorization: Bearer $MEMOS_ACCESS_TOKEN" \
--data "{
\"content\": \"$TEXT\",
\"visibility\": \"$MEMOS_VISIBILITY\"
}")
# 从返回的 JSON 数据中提取 Memos 的 id 值
NEW_MEMOS_ID=$(echo "$MEMOS_RESPONSE" | jq -r '.uid')
# 更新 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"
# POST 到 Sink
if [[ "$SINK_ENABLE" == true ]]; then
SINK_URL="${MASTODON_INSTANCE}@eallion/${LATEST_MASTODON_ID}"
SINK_SLUG="${NEW_MEMOS_ID}"
curl -s -X POST \
-H "authorization: Bearer ${SINK_NUXT_SITE_TOKEN}" \
-H "content-type: application/json" \
-d "{\"url\": \"${SINK_URL}\", \"slug\": \"${SINK_SLUG}\"}" \
"${SINK_HOST}/api/link/create"
fi
# 上传 Statuses 到 s3
if [[ "$S3_ENABLE" == true ]]; then
URL_E5N="${MASTODON_INSTANCE}api/v1/accounts/${MASTODON_ID}/statuses?limit=20&exclude_replies=${SKIP_MASTODON_REPLY}&exclude_reblogs=${SKIP_MASTODON_REBLOG}"
URL_EMOJI="${MASTODON_INSTANCE}api/v1/custom_emojis"
OUTPUT_E5N="statuses"
OUTPUT_EMOJI="custom_emojis"
curl -s "$URL_E5N" > "$OUTPUT_E5N" || { echo "Error fetching e5n data"; exit 1; }
curl -s "$URL_EMOJI" > "$OUTPUT_EMOJI" || { echo "Error fetching e5n data"; exit 1; }
# coscmd upload statuses api/v1/accounts/111136231674527355/ -f -H "{'Content-Type':'application/json'}"
# coscmd upload custom_emojis api/v1/ -f -H "{'Content-Type':'application/json'}"
# tccli teo CreatePurgeTask --cli-unfold-argument --ZoneId zone-2ssxnpo34mu5 --Type purge_host --Method delete --Targets 'mastodon.api.eallion.com'
# aliyun oss cp statuses oss://eallion-com/api/v1/accounts/111136231674527355/ --meta Content-Type:application/json -f
# aliyun oss cp custom_emojis oss://eallion-com/api/v1/ --meta Content-Type:application/json -f
rm statuses
rm custom_emojis
fi
echo "Sync Mastodon to Memos Successful!"
echo "Done: $(TZ=UTC-8 date +"%Y-%m-%d %T")"
echo "============================="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment