NetworkJS-Reloaded

NetworkJS-Reloaded

KubeJS NeoForge 1.21.1 附加组件:在 server_scripts 中支持 HTTP 获取、Discord 机器人和 PostgreSQL。服务器全局或按世界脚本。单人模式:默认网络关闭 — /networkjs enable。

基础库

NetworkJS Reloaded 是 KubeJS 在 NeoForge 1.21.1 上的一个服务端附加模组。它让你的 KubeJS 服务端脚本能够发送 HTTP 请求、连接到 Discord 机器人,并对 PostgreSQL 执行 SQL 查询。你可以像编写其他 KubeJS 脚本一样,在 kubejs/server_scripts 目录下用 JavaScript 编写逻辑。

本项目是 SSnowly 的 NetworkJS 的一个分支,并扩展了 PostgreSQL 支持(连接池、异步查询、预编译语句)。

功能

  • HTTP: 同步和异步请求(fetch, fetchAsync)
  • PostgreSQL: 从脚本中读写数据(postgresQuery, postgresExecute, Postgres)
  • Discord: 发送消息和监听聊天(DiscordBot)
  • 服务器辅助: 广播彩色消息和读取玩家列表(Server)
  • 安全性: 在单人游戏/集成服务器中,网络访问默认关闭,直到管理员运行 /networkjs enable 后才开启

环境要求

  • Minecraft 1.21.1
  • NeoForge 21.1.200 或更新版本(仅限 21.1.x 系列)
  • KubeJS 2101.7.1 或更新版本
  • Java 21
  • 仅服务端(客户端不需要)

安装

  1. 在 mods 文件夹中安装 NeoForge 1.21.1、KubeJS 和本模组。
  2. 重启专用服务器(或你的单人世界)。
  3. 在专用服务器上,功能会自动启用。
  4. 在单人游戏中:运行 /networkjs enable,然后运行 /kubejs reload server。

KubeJS 文件存放位置

脚本可以放在服务器根目录(对所有世界生效)或单个世界的文件夹内(仅对该世界生效)。

服务器全局(建议用于专用服务器):

  • 脚本:kubejs/server_scripts/
  • PostgreSQL 配置:kubejs/config/networkjs/postgres.json(仅限服务器根目录,不要放在世界文件夹内)

仅单个世界:

  • 脚本:world/kubejs/server_scripts/(或你的世界存档路径)

PostgreSQL 配置

NetworkJS 仅从服务器根目录读取一个 JSON 文件:

kubejs/config/networkjs/postgres.json

不要将此文件放在世界文件夹(world/.../kubejs/)内。脚本仍可从任何世界的 server_scripts 使用数据库,但配置文件始终从实例/专用服务器根目录(与 mods/ 同目录)加载。

设置步骤

  1. 如有需要,创建目录结构:kubejs/config/networkjs/
  2. 从仓库复制示例文件: examples/kubejs/config/networkjs/postgres.json.example → kubejs/config/networkjs/postgres.json
  3. 编辑文件(查看下方字段说明)。将 "enabled" 设置为 true。
  4. 重启服务器或运行 /networkjs postgres reload(需要管理员权限)。
  5. 检查:运行 /networkjs postgres status —— 应显示主机、端口和数据库名称。
  6. 在放置好脚本后,运行 /kubejs reload server。

在单人游戏中,需要在数据库查询生效前运行 /networkjs enable。

示例 postgres.json

{
  "enabled": true,
  "host": "localhost",
  "port": 5432,
  "database": "minecraft",
  "username": "postgres",
  "password": "your_secret_here",
  "maxPoolSize": 5,
  "connectionTimeoutMs": 10000
}

配置字段说明

字段 类型 默认值 描述
enabled 布尔值 false true — 在加载/重载时连接。false — PostgreSQL 绑定保持关闭。
host 字符串 localhost PostgreSQL 服务器主机名或 IP。
port 数字 5432 PostgreSQL 端口。
database 字符串 minecraft 数据库名称。
username 字符串 postgres 数据库用户。
password 字符串 "" 数据库密码。请保持此文件的私密性,不要将其提交到 Git 仓库。
maxPoolSize 数字 5 HikariCP 连接池大小(最大并发连接数)。
connectionTimeoutMs 数字 10000 等待从连接池获取连接的超时时间(毫秒)。

模组会自动构建 JDBC URL: jdbc:postgresql://<host>:<port>/<database>

远程数据库示例

{
  "enabled": true,
  "host": "db.example.com",
  "port": 5432,
  "database": "my_server",
  "username": "mc_user",
  "password": "strong_password",
  "maxPoolSize": 10,
  "connectionTimeoutMs": 15000
}

确保 PostgreSQL 接受来自你 Minecraft 服务器 IP 的连接(pg_hba.conf / 防火墙配置)。

连接失败时的排查方法

  • 文件路径必须为 <服务器根目录>/kubejs/config/networkjs/postgres.json
  • "enabled" 必须为 true
  • 密码或数据库名称错误 —— 检查服务器 latest.log 中的 [NetworkJS] / PostgreSQL 错误信息
  • /networkjs status —— 显示注册表状态和 PostgreSQL 连接/断开状态
  • /networkjs postgres reload —— 无需完全重启即可重新读取配置
  • 专用服务器:注册表默认开启;单人游戏需要运行 /networkjs enable

安全注意事项

  • 绝不要在公开的 Issue 追踪器或 Modrinth 评论中粘贴真实密码。
  • 在脚本中使用 SQL 占位符 ? —— 不要直接根据玩家聊天输入构建 SQL。
  • 仅授予数据库用户脚本所需的最小权限(对特定表的 SELECT/INSERT/UPDATE)。

示例

添加脚本后,运行 /kubejs reload server。在单人游戏中,请先运行 /networkjs enable。

异步回调在主线程之外执行 —— 在对聊天、传送或世界进行修改前,请使用 event.server.scheduleInTicks(0, ...)。

HTTP — 同步 GET 请求

PlayerEvents.loggedIn(function (event) {
  var name = String(event.player.username)
  var response = fetch('https://httpbin.org/get')
  if (response.isOk()) {
    event.server.scheduleInTicks(0, function () {
      Server.sendRawMessageToPlayer(name, '&aHTTP &8' + response.getStatus())
    })
  }
})

HTTP — 异步 GET 请求

ServerEvents.loaded(function (event) {
  fetchAsync('https://httpbin.org/get').thenAccept(function (response) {
    console.info('状态: ' + response.getStatus() + ' — 正文长度: ' + response.text().length)
  })
})

HTTP — 携带 JSON 的 POST 请求

ServerEvents.loaded(function (event) {
  fetchAsync('https://httpbin.org/post', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      online: true,
      players: Server.getPlayerCount(),
      names: Server.getPlayerNames()
    })
  }).thenAccept(function (response) {
    console.info('已发表状态: ' + response.getStatus())
  })
})

HTTP — 迷你示例(加入时聊天,多行)

PlayerEvents.loggedIn(function (event) {
  var name = String(event.player.username)
  var r = fetch('https://api.github.com/repos/initialfox/NetworkJS-Reloaded')
  if (!r.isOk()) return
  var repo = JSON.parse(r.text())
  var lines = [
    '&6✦ &aGitHub',
    '&f  ' + repo.full_name,
    '&7  ' + repo.language + ' &8│ &b' + repo.license.spdx_id + ' &8│ &e' + repo.default_branch
  ]
  if (repo.fork && repo.parent) lines.push('&7  fork &f' + repo.parent.full_name)
  event.server.scheduleInTicks(0, function () {
    for (var i = 0; i < lines.length; i++) Server.sendRawMessageToPlayer(name, lines[i])
  })
})

PostgreSQL — 加入时查询 SELECT

PlayerEvents.loggedIn(function (event) {
  var name = String(event.player.username)
  postgresQuery('SELECT username, role FROM users WHERE username = ? LIMIT 1', [name])
    .thenAccept(function (result) {
      event.server.scheduleInTicks(0, function () {
        if (!result.isOk()) {
          Server.sendRawMessageToPlayer(name, '&c数据库错误: ' + result.getError())
          return
        }
        if (result.getRowCount() === 0) {
          Server.sendRawMessageToPlayer(name, '&e数据库中未找到该玩家的记录')
          return
        }
        var row = result.getRows().get(0)
        Server.sendRawMessageToPlayer(name, '&a角色: &e' + row.get('role'))
      })
    })
})

PostgreSQL — 插入会话记录 (INSERT)

PlayerEvents.loggedIn(function (event) {
  var uuid = String(event.player.uuid)
  var name = String(event.player.username)
  postgresExecute(
    'INSERT INTO player_sessions (uuid, username, joined_at) VALUES (?, ?, NOW())',
    [uuid, name]
  ).thenAccept(function (result) {
    if (result.isOk()) {
      console.info('会话插入成功,影响行数: ' + result.getUpdateCount())
    }
  })
})

PostgreSQL — 类 API (Postgres)

PlayerEvents.loggedOut(function (event) {
  var uuid = String(event.player.uuid)
  Postgres.executeAsync(
    'UPDATE player_sessions SET left_at = NOW() WHERE uuid = ? AND left_at IS NULL',
    [uuid]
  )
})

PostgreSQL — 检查连接状态

ServerEvents.loaded(function (event) {
  if (Postgres.isConnected()) {
    console.info('[NetworkJS] PostgreSQL 连接池已就绪')
  } else {
    console.warn('[NetworkJS] PostgreSQL 未连接 — 请检查 kubejs/config/networkjs/postgres.json')
  }
})

Discord — 发送消息

var bot = new DiscordBot({
  token: '你的BOT_TOKEN',
  guild: '服务器ID',
  channels: { chat: '频道ID', log: '另一个频道ID' }
})

ServerEvents.loaded(function (event) {
  bot.sendMessage('log', '服务器已启动!')
})

Discord — 发送嵌入消息 (Embed)

bot.sendEmbed('chat', {
  title: '服务器在线',
  description: '玩家数量: ' + Server.getPlayerCount(),
  color: 0x00ff00
})

Discord — Minecraft 聊天转发到 Discord

PlayerEvents.chat(function (event) {
  var player = String(event.player.username)
  var msg = String(event.message)
  bot.sendMessage('chat', '**' + player + '**: ' + msg)
})

Discord — Discord 消息转发到 Minecraft

bot.onMessage(function (msg) {
  if (msg.isFromConfiguredChannel() && !msg.isBot()) {
    Server.sendRawMessage('&9[Discord] &f' + msg.getAuthor() + ': ' + msg.getContent())
  }
})

服务器 — 广播和玩家列表

ServerEvents.loaded(function (event) {
  Server.sendRawMessage('&a[NetworkJS] &7服务器已加载。在线玩家: &f' + Server.getPlayerCount())
  var names = Server.getPlayerNames()
  console.info('玩家列表: ' + names)
})

服务器 — 向单个玩家发送消息

PlayerEvents.loggedIn(function (event) {
  Server.sendRawMessageToPlayer(event.player.username, '&a欢迎! &7NetworkJS 已激活。')
})

命令(需要管理员权限,等级 2)

  • /networkjs enable — 启用网络绑定(单人游戏)
  • /networkjs disable — 禁用网络访问
  • /networkjs reload — 重载绑定
  • /networkjs status — 显示注册表和数据库状态
  • /networkjs postgres reload — 重载数据库配置
  • /networkjs postgres status — 显示数据库连接信息

链接

许可证: MIT