0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マイクラのログインをdiscordに通知する【シェルスクリプト】

Posted at

はじめに

注意事項

素人の書いたコードです
ソースコードは、動作保証はありません
使った結果、何か問題が起きても責任は負えませんので、自己責任でお願いします

想定読者:cd,ls程度はわかる、ssh接続もわかる、シェルスクリプトもわかる


マインクラフトが動いているVPS上でシェルスクリプトを実行します
そのため、マインクラフトが動いているサーバーに乗り込む必要があります

ssh接続で乗り込んでください
conoha for gameなどはssh接続するのに、セキュリティグループの設定とsshサーバーの起動が必要です
ConoHa for Game(VPS)にSFTP接続する方法 - GameServer8 ゲームサーバーエイト

conoha for gameの場合はブラウザからコンソール画面が開けるのでそれでもいいです
(操作性が微妙なのでおすすめはしない)

余談ですが、conoha for gameconoha for VPSは別物です。注意しましょう

動機

  • マインクラフトのログイン通知をdiscordに出したい
  • シェルスクリプト採用理由・方針
    • ゲーム用レンタルサーバーで動かす為、負荷を減らしたい
    • 環境整備などを避けたい
      • pythonのライブラリインストール作業・jqのインストール作業 など(深い理由はない)

本文

仕様

  • ログインとログアウトをdiscordにメッセージを送信
  • ログインは通知ありで送信。ログアウトはsilentメッセージで送信
  • 深夜の特定時間は止める(うるさいから)
  • discordのメッセージは毎日朝に全て削除される

silentメッセージ:通知を出さずにメッセージを送れる。ディスコードの標準機能。LINEにもあるやつ

構成

マインクラフトが動いているVPS上でシェルスクリプトを実行

  • crontabによってcheck_in_out.shを毎分実行
    • setting/player_list.txtのプレイヤーIDとログイン状態を読み取る
    • プレイヤーのログインログアウト状態に変更があった場合discordに送信
    • 送信時、setting/player_list.txtのプレイヤーIDとログイン状態を更新
    • 送信時、メッセージIDをsetting/message_ids.txtに保存
  • crontabによってdelete_mes.sh を毎日朝9時に実行
    • setting/message_ids.txtのメッセージIDを読み取り、メッセージ削除

メッセージの送信・削除にはcurlwebhookを利用

ファイルで各自、書き換える場所

基本的に以下のスクリプトはコピペで動きますが、書き換えるべき場所がいくつかあるので記載します

config.sh:全般
check_in_out.shsource "/root/world_observer/setting/config.sh"のパス
delete_mes.shsource "/root/world_observer/setting/config.sh"のパス
player_list.txt:ID(IDはマイクラログイン時に頭の上に出るやつ、記載したIDに対してログイン、アウトが監視されるようになる)

crontabで動かす関係上、全体的にプルパスで記載することを推奨
実行タイミングを変えたい場合はinit.shcrontab -eで編集をしてください

webhookがよくわからない人はこの記事一番後ろに乗せてるURLの最初のやつみましょう

ファイル構成

~/world_observer
    |--check_in_out.sh              # ログインログアウトを監視・通知の送信
    |--delete_mes.sh                # メッセージの削除
    |--init.sh                      # 初期設定(一度のみ実行)
    |--setting/                     # 設定ファイルなどを保存するディレクトリ
       |--config.sh                 # パスの設定用ファイル
       |--message_ids.txt           # 削除用のメッセージIDを保存するtxt
       |--player_list.txt           # プレイヤーIDとログイン状態を保存するtxt

config.sh:パスの設定用ファイル

config.sh
#!/bin/bash
#################################################

# webhook:通知したいサーバーに応じて変更する
WEBHOOK_URL="https://discord.com/api/webhooks/0000/AAAABBBBCCCCZZZZ"

# このスクリプトを配置した場所
pwd_path="/root/world_observer"
# logファイルの場所(マインクラフトサーバーの動いている配下にある)
logfile="/opt/minecraft_server/logs/latest.log"

# 書き換え不要
status_file="$pwd_path/setting/player_list.txt"
message_ids_file="$pwd_path/setting/message_ids.txt"

#################################################

check_in_out.sh:ログインログアウトを監視・通知の送信

check_in_out.sh
#!/bin/bash
#################################################
source "/root/world_observer/setting/config.sh"
#################################################

# メッセージを送信する関数 引数:ユーザーID,退出フラグ,通知フラグ
sent_message_discord (){
    # ユーザー名に含まれる[_]をエスケープ\
    name_mes=$(echo "${1}" | sed 's/_/\\\\_/g')
    in_out=${2}
    notification_flag=${3}
    main_mess="${name_mes}    -    [ ${in_out} ]"

    # 通知するか否か
    if "$notification_flag"; then
        REQUEST_BODY="{\"content\":\"$main_mess\"}"
    else
        REQUEST_BODY="{\"content\":\"$main_mess\",\"flags\": 4096}"
    fi
    # メッセージの送信
    RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$REQUEST_BODY" "$WEBHOOK_URL?wait=true")

    # curl結果からメッセージIDをを取り出す
    MESSAGE_ID=$(echo "$RESPONSE" | grep -o ',"id":"[0-9]*"' | sed -E 's/,"id":"([0-9]*)"/\1/')
    # メッセージIDをtxtに保存する
    if [[ -n "$MESSAGE_ID" ]]; then
        echo "$MESSAGE_ID" >>$message_ids_file
    fi
}

main () {

    player_num=0
    update_flag=false
    # テキストファイルからプレイヤーID、ログイン状態、プレイヤーの人数を読み取る
    if [[ -f "$status_file" ]]; then
        while IFS=' ' read -r key value; do
            player_name[${player_num}]=$key
            player_stat[${player_num}]=$value
            ((player_num++))
        done < "$status_file"
    fi
    # プレイヤーの数だけ実行
    for ((i = 0; i < player_num; i++)); do
        # ログには下のような記載がある。ログの中から最後の left か joined を変数に保存
        # [02:33:09] [Server thread/INFO]: Player_ID_A joined the game
        in_out_st=$(tail -n 100 "$logfile" | grep -E "${player_name[i]} (left|joined) the game" | awk 'END {print $5}')

        # プレイヤーがINしている状態で ワールドからleftした時
        if [[ "$in_out_st" == "left" && "${player_stat[i]}" == "IN" ]]; then
            # メッセージの送信関数呼び出し,ログイン状態の更新,アップデートフラグをtrue
            sent_message_discord ${player_name[i]} "OUT" false
            player_stat[i]="OUT"
            update_flag=true
        # プレイヤーがOUTしている状態で ワールドからjoinedした時
        elif [[ "$in_out_st" == "joined"  && "${player_stat[i]}" == "OUT"  ]]; then
            # メッセージの送信関数呼び出し,ログイン状態の更新,アップデートフラグをtrue
            sent_message_discord ${player_name[i]} "IN" true
            player_stat[i]="IN"
            update_flag=true
        fi
    done
    # ログイン状態の更新が行われている時、テキストファイルを更新する
    if "$update_flag"; then
        for ((i = 0; i < player_num; i++)); do
            echo "${player_name[i]} ${player_stat[i]}">>./tmp.tmp
        done
        mv ./tmp.tmp "$status_file"
    fi
}

main

delete_mes.sh:メッセージの削除

delete_mes.sh
#!/bin/bash
#################################################
source "/root/world_observer/setting/config.sh"
#################################################

# ファイルがある時、ファイルに保存してあるメッセージIDのメッセージを削除してファイルを空にする
if [[ -f "$message_ids_file" ]]; then
    while read -r MESSAGE_ID; do
        curl -X DELETE "$WEBHOOK_URL/messages/$MESSAGE_ID"
    done < "$message_ids_file"
    rm -f "$message_ids_file"
    touch "$message_ids_file"
fi

init.sh:初期設定(一度のみ実行)
間違えて2回以上実行した場合は、多重に実行されてしまうため、crontab -eで内容を編集してください

init.sh
#!/bin/bash
#################################################
source "/root/world_observer/setting/config.sh"
#################################################

file1=check_in_out.sh 
file2=delete_mes.sh 
chmod 755 $file1
chmod 755 $file2 

# cronを使って定期実行を設定 crontab -e で手動で操作してもいい
# 分  時  日  月  曜日  コマンド
# 0時と9時から23時の1分ごとに監視 (1:00から8:59は実行しない)
(echo "* 0,9-23 * * * $pwd_path/$file1"; crontab -l) | crontab -
# 9時1分に削除コマンドを走らせる
(echo "1 9 * * * $pwd_path/$file2"; crontab -l) | crontab -

echo "----------------------------------------------"
echo "Don't run init.sh ※※ twice ※※"
echo "You can check and edit[crontab -e]"
echo "----------------------------------------------"

player_list.txtプレイヤーIDとログイン状態を保存するtxt
(当たり前だが、Player_A_IDは書き換える)

player_list.txt
Player_A_ID OUT
Player_B_ID OUT

message_ids.txt:削除用のメッセージIDを保存するtxt(空ファイル)

message_ids.txt

おわりに

感想

以下は雑に書いた感想なので読ませる文章ではないですが、参考までに乗せておきます

  • player_list.txtは普通にcsvにした方が良さそうですよね、後から思いました
  • いろいろやってたら思ったよりファイルが増えちゃった...
  • AutoDeleteというメッセージ自動削除のdiscord botがあるけど、今は動かないらしい
  • 多分pythonかdiscord.pyでやった方が素直
  • 23時にログインして、2時にログアウトすると朝の9時にログアウト通知が来るのは気持ち悪い(深夜にログアウトするとログは残るが、ステータスの変更スクリプトが走っていないため)
    • 9時1分にメッセージは消えるし、通知もサイレントなので運用的に困ることはないはず
      • 最初はシェルスクリプトの中で現在時刻を取得し、分岐を組んでたが、途中からcrontabに変更した弊害
  • マイクラのIDにアンダースコア(_)で囲まれている部分があると、discord上で斜体になってしまう。それを回避するためにこのソースコードではエスケープを施している、ただ、jsonの中でもエスケープとして機能してしまうため二重にエスケープしてる
    • sed 's/_/\\\\_/g'の部分のこと _\\\\_に置換
    • アスタリスク(*)なんかでも同様のことが起きると思うが、IDにアスタリスクは珍しいと思うのでやってない
      • IDにアンダースコアが一つのみしか含まれてない時にエスケープしても大丈夫なはず。今discordで試した。でもスクリプト上だともしかしたらバグるかも
  • ログファイルをtailで読み取ってるのはログファイルが長い時に処理が重くなるのを警戒してですが、マイクラのログファイルってあんま長くならないし、定期的にアーカイブされて別ファイルになるから杞憂
  • メッセージを消してるのに深い理由はないのですが、あまり誰がどれだけログインしたかが、後から辿れない方がいいかなと思ったためです(毎日消えるのが逆に嫌ならcrontabを月一とかで動かしてもいいし、ソースを改変して、メッセージIDの取得と書き出し、メッセージ削除の部分を消せばいいと思います)
  • あとplayer_list.txtのファイル末尾に改行がないとエラー起きるので注意です
ファイル改行のイメージ(ファイル末尾に改行が入っているかどうかの例です)
# NG:改行されてない
root@vm-xxxxxx:~# echo -n aaa > xxx
root@vm-xxxxxx:~# cat xxx 
aaaroot@vm-xxxxxx:~# echo aaa > yyy

# OK:改行されてる
root@vm-xxxxxx:~# cat yyy 
aaa
root@vm-xxxxxx:~# 

参考URL

外部サービスからDiscordにメッセージを送る(Webhook)

DiscordのWebhookでSilentMessageを送信する方法 | こたのお考え

Discord APIの新しいWebhookエンドポイント(EditとDelete) #discord - Qiita

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?