Skip to content

Commit

Permalink
feat(#109): add-whitelist (#113)
Browse files Browse the repository at this point in the history
新增白名单模式
在配置文件新增rcon.kick用于配置白名单开关在自动刷新用户列表的时候如果配置了白名单会自动踢出没在白名单的玩家
  • Loading branch information
if1024 authored Feb 5, 2024
1 parent 3fa3fe7 commit eaf8df1
Show file tree
Hide file tree
Showing 9 changed files with 554 additions and 1 deletion.
48 changes: 48 additions & 0 deletions api/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,51 @@ func banPlayer(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{"success": true})
}

func addWhite(c *gin.Context) {
var player database.PlayerW
if err := c.ShouldBindJSON(&player); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := service.AddWhitelist(database.GetDB(), player); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}

func listWhite(c *gin.Context) {
players, err := service.ListWhitelist(database.GetDB())
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, players)
}

func removeWhite(c *gin.Context) {
var player database.PlayerW
if err := c.ShouldBindJSON(&player); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := service.RemoveWhitelist(database.GetDB(), player); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}

func putWhite(c *gin.Context) {
var players []database.PlayerW
if err := c.ShouldBindJSON(&players); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := service.PutWhitelist(database.GetDB(), players); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}
4 changes: 4 additions & 0 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func RegisterRouter() *gin.Engine {
authGroup.POST("/player/:player_uid/ban", banPlayer)
authGroup.PUT("/guild", putGuilds)
authGroup.POST("/sync", syncData)
authGroup.GET("/whitelist", listWhite)
authGroup.POST("/whitelist", addWhite)
authGroup.DELETE("/whitelist", removeWhite)
authGroup.PUT("/whitelist", putWhite)
}

return r
Expand Down
6 changes: 6 additions & 0 deletions internal/database/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,9 @@ type Guild struct {
Players []*GuildPlayer `json:"players"`
BaseIds []string `json:"base_ids"`
}

type PlayerW struct {
Name string `json:"name"`
SteamID string `json:"steam_id"`
PlayerUID string `json:"player_uid"`
}
14 changes: 14 additions & 0 deletions internal/task/task.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package task

import (
"github.com/zaigie/palworld-server-tool/internal/database"
"time"

"github.com/go-co-op/gocron/v2"
Expand All @@ -24,6 +25,19 @@ func RconSync(db *bbolt.DB) {
logger.Error(err)
}
logger.Info("Rcon sync done\n")

kickInterval := viper.GetBool("rcon.kick")
if kickInterval {
go CheckAndKickPlayers(db, playersRcon)
}
}

func CheckAndKickPlayers(db *bbolt.DB, players []database.PlayerRcon) {
err := tool.CheckAndKickPlayers(db, players)
if err != nil {
logger.Error(err)
}
logger.Info("Check whitelist done\n")
}

func SavSync() {
Expand Down
34 changes: 34 additions & 0 deletions internal/tool/rcon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package tool
import (
"errors"
"fmt"
"github.com/zaigie/palworld-server-tool/service"
"go.etcd.io/bbolt"
"regexp"
"strings"

Expand Down Expand Up @@ -98,6 +100,38 @@ func ShowPlayers() ([]database.PlayerRcon, error) {
return playersRcon, nil
}

func CheckAndKickPlayers(db *bbolt.DB, players []database.PlayerRcon) error {
whitelist, err := service.ListWhitelist(db)
if err != nil {
return errors.New(err.Error())
}
for _, player := range players {
if !isPlayerWhitelisted(player, whitelist) {
// 优先使用SteamId进行操作,如果没有提供,则使用PlayerUid
identifier := player.SteamId
if identifier == "" {
identifier = player.PlayerUid
}
if err := KickPlayer(identifier); err != nil {
logger.Warnf("Kicked %s fail, %s \n", player.Nickname, err)
} else {
logger.Warnf("Kicked %s successful \n", player.Nickname)
}
}
}
return nil
}

func isPlayerWhitelisted(player database.PlayerRcon, whitelist []database.PlayerW) bool {
for _, whitelistedPlayer := range whitelist {
if (player.PlayerUid != "" && player.PlayerUid == whitelistedPlayer.PlayerUID) ||
(player.SteamId != "" && player.SteamId == whitelistedPlayer.SteamID) {
return true
}
}
return false
}

func KickPlayer(steamID string) error {
exec, response, err := executeCommand("KickPlayer " + steamID)
if err != nil {
Expand Down
154 changes: 154 additions & 0 deletions service/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package service

import (
"encoding/json"
"errors"
"strings"
"time"

Expand Down Expand Up @@ -160,3 +161,156 @@ func GetPlayer(db *bbolt.DB, playerUid string) (database.Player, error) {
}
return player, nil
}

func AddWhitelist(db *bbolt.DB, player database.PlayerW) error {
return db.Update(func(tx *bbolt.Tx) error {
// 获取或创建白名单bucket
b, err := tx.CreateBucketIfNotExists([]byte("whitelist"))
if err != nil {
return err
}

// 序列化玩家数据为JSON
playerData, err := json.Marshal(player)
if err != nil {
return err
}

// 使用 findPlayerKey 检查玩家是否已经在白名单中
key, err := findPlayerKey(b, player)
if err != nil {
return err
}

// 如果玩家已存在,更新其信息;如果不存在,创建新的键
if key != nil {
// 玩家已存在,更新其信息
if err := b.Put(key, playerData); err != nil {
return err
}
} else {
// 玩家不存在,添加新玩家
// 生成新玩家的唯一键
newPlayerKey := []byte(player.Name + "|" + player.SteamID + "|" + player.PlayerUID)
if err := b.Put(newPlayerKey, playerData); err != nil {
return err
}
}

return nil
})
}

func ListWhitelist(db *bbolt.DB) ([]database.PlayerW, error) {
var players []database.PlayerW

err := db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("whitelist"))
if b == nil {
return nil // No error, just an empty list if the bucket doesn't exist.
}

return b.ForEach(func(k, v []byte) error {
var player database.PlayerW
if err := json.Unmarshal(v, &player); err != nil {
return err
}
players = append(players, player)
return nil
})
})

return players, err
}

// findPlayerKey tries to find a player in the whitelist and returns the key if found.
func findPlayerKey(b *bbolt.Bucket, player database.PlayerW) ([]byte, error) {
var keyFound []byte
err := b.ForEach(func(k, v []byte) error {
var existingPlayer database.PlayerW
if err := json.Unmarshal(v, &existingPlayer); err != nil {
return err
}
if matchesCriteria(existingPlayer, player) {
keyFound = append([]byte(nil), k...) // Make a copy of the key
return errors.New("player found") // Use an error to break out of the iteration early.
}
return nil
})

if err != nil && err.Error() == "player found" {
return keyFound, nil
}

return nil, err
}

// RemoveWhitelist removes a player from the whitelist.
func RemoveWhitelist(db *bbolt.DB, player database.PlayerW) error {
return db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("whitelist"))
if b == nil {
return errors.New("whitelist bucket does not exist")
}

key, err := findPlayerKey(b, player)
if err != nil {
return err
}
if key == nil {
return errors.New("player not found in whitelist")
}

return b.Delete(key)
})
}

// matchesCriteria checks if the given player matches the criteria.
func matchesCriteria(existingPlayer, player database.PlayerW) bool {
// 如果PlayerUID非空且匹配,认为是同一个玩家
if player.PlayerUID != "" && existingPlayer.PlayerUID == player.PlayerUID {
return true
}
// 如果Name非空且匹配,认为是同一个玩家
if player.Name != "" && existingPlayer.Name == player.Name {
return true
}
// 如果SteamID非空且匹配,认为是同一个玩家
if player.SteamID != "" && existingPlayer.SteamID == player.SteamID {
return true
}
// 如果没有任何字段匹配,返回false
return false
}

func PutWhitelist(db *bbolt.DB, players []database.PlayerW) error {
return db.Update(func(tx *bbolt.Tx) error {
// 获取或创建白名单bucket
b, err := tx.CreateBucketIfNotExists([]byte("whitelist"))
if err != nil {
return err
}

// 清空现有的白名单
err = b.ForEach(func(k, v []byte) error {
return b.Delete(k)
})
if err != nil {
return err
}

// 遍历并添加新的玩家数据到白名单
for _, player := range players {
playerData, err := json.Marshal(player)
if err != nil {
return err
}
// 生成新玩家的唯一键,这里假设player.PlayerUID是唯一的
if err := b.Put([]byte(player.PlayerUID), playerData); err != nil {
return err
}
}

return nil
})
}
28 changes: 28 additions & 0 deletions web/src/assets/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const messages = {
modal: {
auth: "Auth",
broadcast: "Publish Broadcast",
whitelist: "Whitelist",
addWhitelist: "Add Whitelist",
},
status: {
online: "Online",
Expand Down Expand Up @@ -43,6 +45,11 @@ const messages = {
shutdownfail: "Shutdown fail: {err}",
shutdowntip:
"This will shut down the server after 60 seconds and send a broadcast.",
selectVerify: "Select verification method",
addwhitesuccess: "Add whitelist success!",
addwhitefail: "Add whitelist fail: {err}",
removewhitesuccess: "Remove whitelist success!",
removewhitefail: "Remove whitelist fail: {err}",
},
button: {
auth: "Admin Mode",
Expand All @@ -57,8 +64,15 @@ const messages = {
cancel: "Cancel",
viewGuild: "View Guild",
viewPlayer: "View Player",
whitelist: "Whitelist",
joinWhitelist: "Join Whitelist",
search: "Search",
},
input: {
nickname: "Nickname",
player_uid: "Player Uid",
steam_id: "Steam Id",
},
pal: {
type: "Type",
level: "Level",
Expand All @@ -79,6 +93,8 @@ const messages = {
modal: {
auth: "管理认证",
broadcast: "发布游戏内广播",
whitelist: "白名单管理",
addWhitelist: "添加白名单",
},
status: {
online: "在线",
Expand Down Expand Up @@ -115,6 +131,11 @@ const messages = {
broadcastasciierr: "广播只支持ASCII字符,暂不支持中文",
shutdownfail: "关闭失败: {err}",
shutdowntip: "此操作将在60秒后关闭服务器并发送广播。",
selectVerify: "请选择验证方式",
addwhitesuccess: "添加白名单成功!",
addwhitefail: "添加白名单失败: {err}",
removewhitesuccess: "移除白名单成功!",
removewhitefail: "移除白名单失败: {err}",
},
button: {
auth: "管理模式",
Expand All @@ -129,8 +150,15 @@ const messages = {
cancel: "取消",
viewGuild: "查看公会",
viewPlayer: "查看玩家",
whitelist: "白名单管理",
joinWhitelist: "加入白名单",
search: "搜索",
},
input: {
nickname: "昵称",
player_uid: "玩家 Uid",
steam_id: "Steam Id",
},
pal: {
type: "类型",
level: "等级",
Expand Down
Loading

0 comments on commit eaf8df1

Please sign in to comment.