はじめに
注意事項
素人の書いたコードです
ソースコードは、動作保証はありません
使った結果、何か問題が起きても責任は負えませんので、自己責任でお願いします
想定読者:cd
,ls
程度はわかる、ssh接続
もわかる、シェルスクリプト
もわかる
マインクラフトが動いているVPS上でシェルスクリプトを実行します
そのため、マインクラフトが動いているサーバーに乗り込む必要があります
ssh接続で乗り込んでください
conoha for game
などはssh接続するのに、セキュリティグループの設定とsshサーバーの起動が必要です
ConoHa for Game(VPS)にSFTP接続する方法 - GameServer8 ゲームサーバーエイト
conoha for game
の場合はブラウザからコンソール画面が開けるのでそれでもいいです
(操作性が微妙なのでおすすめはしない)
余談ですが、conoha for game
とconoha 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を読み取り、メッセージ削除
-
メッセージの送信・削除にはcurl
とwebhook
を利用
ファイルで各自、書き換える場所
基本的に以下のスクリプトはコピペで動きますが、書き換えるべき場所がいくつかあるので記載します
config.sh
:全般
check_in_out.sh
:source "/root/world_observer/setting/config.sh"
のパス
delete_mes.sh
:source "/root/world_observer/setting/config.sh"
のパス
player_list.txt
:ID(IDはマイクラログイン時に頭の上に出るやつ、記載したIDに対してログイン、アウトが監視されるようになる)
crontab
で動かす関係上、全体的にプルパスで記載することを推奨
実行タイミングを変えたい場合はinit.sh
やcrontab -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
:パスの設定用ファイル
#!/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
:ログインログアウトを監視・通知の送信
#!/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
:メッセージの削除
#!/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
で内容を編集してください
#!/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_A_ID OUT
Player_B_ID OUT
message_ids.txt
:削除用のメッセージIDを保存するtxt(空ファイル)
おわりに
感想
以下は雑に書いた感想なので読ませる文章ではないですが、参考までに乗せておきます
-
player_list.txt
は普通にcsvにした方が良さそうですよね、後から思いました - いろいろやってたら思ったよりファイルが増えちゃった...
-
AutoDelete
というメッセージ自動削除のdiscord botがあるけど、今は動かないらしい - 多分pythonかdiscord.pyでやった方が素直
- 23時にログインして、2時にログアウトすると朝の9時にログアウト通知が来るのは気持ち悪い(深夜にログアウトするとログは残るが、ステータスの変更スクリプトが走っていないため)
- 9時1分にメッセージは消えるし、通知もサイレントなので運用的に困ることはないはず
- 最初はシェルスクリプトの中で現在時刻を取得し、分岐を組んでたが、途中からcrontabに変更した弊害
- 9時1分にメッセージは消えるし、通知もサイレントなので運用的に困ることはないはず
- マイクラの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)