diff --git a/README.md b/README.md index ea3f6988..3e261b7d 100644 --- a/README.md +++ b/README.md @@ -467,6 +467,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi | `POST` | `"/clientIps/:email"` | Client Ip address | | `POST` | `"/clearClientIps/:email"` | Clear Client Ip address | | `POST` | `"/addClient"` | Add Client to inbound | +| `POST` | `"/addClientInbounds"` | Add Client to inbounds | | `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* | | `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* | | `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic | diff --git a/web/controller/api.go b/web/controller/api.go index 9944e2a3..8e98bc9d 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -40,6 +40,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) { {"POST", "/clientIps/:email", a.inboundController.getClientIps}, {"POST", "/clearClientIps/:email", a.inboundController.clearClientIps}, {"POST", "/addClient", a.inboundController.addInboundClient}, + {"POST", "/addClientInbounds", a.inboundController.addClientToMultipleInbounds}, {"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient}, {"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient}, {"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic}, diff --git a/web/controller/inbound.go b/web/controller/inbound.go index c22ce192..7db6feec 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -17,6 +17,12 @@ type InboundController struct { xrayService service.XrayService } +type AddClientPayload struct { + Id int `json:"id"` + Settings string `json:"settings"` + InboundIds []int `json:"inboundIds"` +} + func NewInboundController(g *gin.RouterGroup) *InboundController { a := &InboundController{} a.initRouter(g) @@ -33,6 +39,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/addClient", a.addInboundClient) + g.POST("/addClientInbounds", a.addClientToMultipleInbounds) g.POST("/:id/delClient/:clientId", a.delInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) @@ -190,6 +197,47 @@ func (a *InboundController) addInboundClient(c *gin.Context) { } } + +func (a *InboundController) addClientToMultipleInbounds(c *gin.Context) { + var payload AddClientPayload + if err := c.ShouldBindJSON(&payload); err != nil { + jsonMsg(c, "Invalid request payload", err) + return + } + + data := &model.Inbound{ + Id: payload.Id, + Settings: payload.Settings, + } + + // If no specific inbound IDs are provided, add to all inbounds + if len(payload.InboundIds) == 0 { + allInbounds, err := a.inboundService.GetAllInbounds() + if err != nil { + jsonMsg(c, "Could not retrieve inbounds", err) + return + } + + for _, inbound := range allInbounds { + payload.InboundIds = append(payload.InboundIds, inbound.Id) + } + } + + needRestart, err := a.inboundService.AddClientToMultipleInbounds(data, payload.InboundIds) + if err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } + + jsonMsg(c, "Client(s) added to multiple inbounds", nil) + if needRestart { + a.xrayService.SetToNeedRestart() + } +} + + + + func (a *InboundController) delInboundClient(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/web/service/inbound.go b/web/service/inbound.go index 25a43f47..8c8ef58e 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -510,6 +510,114 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) { return needRestart, tx.Save(oldInbound).Error } + +func (s *InboundService) AddClientToMultipleInbounds(data *model.Inbound, inboundIds []int) (bool, error) { + clients, err := s.GetClients(data) + if err != nil { + return false, err + } + + var settings map[string]interface{} + err = json.Unmarshal([]byte(data.Settings), &settings) + if err != nil { + return false, err + } + + interfaceClients := settings["clients"].([]interface{}) + + needRestart := false + db := database.GetDB() + tx := db.Begin() + + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + + for _, inboundId := range inboundIds { + oldInbound, err := s.GetInbound(inboundId) + if err != nil { + return false, err + } + + for _, client := range interfaceClients { + clientMap, ok := client.(map[string]interface{}) + if !ok { + return false, common.NewError("Invalid client format") + } + for _, cl := range clients { + newEmail := cl.Email + "_" + strconv.Itoa(inboundId) + existEmail, err := s.checkEmailsExistForClients([]model.Client{{Email: newEmail}}) + if err != nil { + return false, err + } + if existEmail != "" { + return false, common.NewError("Duplicate email:", existEmail) + } + clientMap["email"] = newEmail + } + } + + var oldSettings map[string]interface{} + err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) + if err != nil { + return false, err + } + + oldClients := oldSettings["clients"].([]interface{}) + oldClients = append(oldClients, interfaceClients...) + oldSettings["clients"] = oldClients + + newSettings, err := json.MarshalIndent(oldSettings, "", " ") + if err != nil { + return false, err + } + + oldInbound.Settings = string(newSettings) + + s.xrayApi.Init(p.GetAPIPort()) + for _, client := range clients { + client.Email = client.Email + "_" + strconv.Itoa(inboundId) + if len(client.Email) > 0 { + s.AddClientStat(tx, inboundId, &client) + if client.Enable { + cipher := "" + if oldInbound.Protocol == "shadowsocks" { + cipher = oldSettings["method"].(string) + } + err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{ + "email": client.Email, + "id": client.ID, + "flow": client.Flow, + "password": client.Password, + "cipher": cipher, + }) + if err1 != nil { + logger.Debug("Error in adding client by api:", err1) + needRestart = true + } else { + logger.Debug("Client added by api:", client.Email) + } + } + } else { + needRestart = true + } + } + + if err := tx.Save(oldInbound).Error; err != nil { + return false, err + } + s.xrayApi.Close() + } + + return needRestart, nil +} + + + func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, error) { oldInbound, err := s.GetInbound(inboundId) if err != nil {