From e299365c626524988608cf7970e15b2675a90603 Mon Sep 17 00:00:00 2001 From: Javad Tinatpak Date: Tue, 6 Aug 2024 13:39:37 +0330 Subject: [PATCH 1/7] API - Add Client to inbounds --- README.md | 1 + web/controller/api.go | 1 + web/controller/inbound.go | 48 +++++++++++++++++ web/service/inbound.go | 108 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+) 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 { From b3e1052c6189f67cb78fc82fa29ea2cc0c9ef912 Mon Sep 17 00:00:00 2001 From: Javad Tinatpak Date: Tue, 6 Aug 2024 16:37:12 +0330 Subject: [PATCH 2/7] API - Update Client to inbounds --- README.md | 3 +- web/controller/api.go | 1 + web/controller/inbound.go | 42 +++++++++- web/service/inbound.go | 156 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3e261b7d..7ae5f374 100644 --- a/README.md +++ b/README.md @@ -459,7 +459,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi | `GET` | `"/list"` | Get all inbounds | | `GET` | `"/get/:id"` | Get inbound with inbound.id | | `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email | -| `GET` | `"/getClientTrafficsById/:id"` | Get client's traffic By ID | +| `GET` | `"/getClientTrafficsById/:id"` | Get client's traffic By ID | | `GET` | `"/createbackup"` | Telegram bot sends backup to admins | | `POST` | `"/add"` | Add inbound | | `POST` | `"/del/:id"` | Delete Inbound | @@ -470,6 +470,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi | `POST` | `"/addClientInbounds"` | Add Client to inbounds | | `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* | | `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* | +| `POST` | `"/updateClientInbounds/:subId"` | Update Client by subId\* | | `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic | | `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds | | `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound | diff --git a/web/controller/api.go b/web/controller/api.go index 8e98bc9d..0ddb879b 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -43,6 +43,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) { {"POST", "/addClientInbounds", a.inboundController.addClientToMultipleInbounds}, {"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient}, {"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient}, + {"POST", "/updateClientInbounds/:subId", a.inboundController.updateClientInMultipleInbounds}, {"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic}, {"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics}, {"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 7db6feec..93b409e2 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -18,7 +18,6 @@ type InboundController struct { } type AddClientPayload struct { - Id int `json:"id"` Settings string `json:"settings"` InboundIds []int `json:"inboundIds"` } @@ -42,6 +41,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/addClientInbounds", a.addClientToMultipleInbounds) g.POST("/:id/delClient/:clientId", a.delInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient) + g.POST("/updateClientInbounds/:subId", a.updateClientInMultipleInbounds) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/resetAllTraffics", a.resetAllTraffics) g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) @@ -206,7 +206,6 @@ func (a *InboundController) addClientToMultipleInbounds(c *gin.Context) { } data := &model.Inbound{ - Id: payload.Id, Settings: payload.Settings, } @@ -236,6 +235,45 @@ func (a *InboundController) addClientToMultipleInbounds(c *gin.Context) { } +func (a *InboundController) updateClientInMultipleInbounds(c *gin.Context) { + var payload AddClientPayload + subId := c.Param("subId") + + if err := c.ShouldBindJSON(&payload); err != nil { + jsonMsg(c, "Invalid request payload", err) + return + } + + data := &model.Inbound{ + 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 := true + + needRestart, err = a.inboundService.UpdateClientInMultipleInbounds(data, subId, payload.InboundIds) + if err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } + jsonMsg(c, "Client updated in multiple inbounds", nil) + if needRestart { + a.xrayService.SetToNeedRestart() + } +} + func (a *InboundController) delInboundClient(c *gin.Context) { diff --git a/web/service/inbound.go b/web/service/inbound.go index 8c8ef58e..bc1a902a 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -838,6 +838,162 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin return needRestart, tx.Save(oldInbound).Error } +func (s *InboundService) UpdateClientInMultipleInbounds(data *model.Inbound, subId string, inboundIds []int) (bool, error) { + var needRestart bool + + for _, inboundId := range inboundIds { + inbound, err := s.GetInbound(inboundId) + if err != nil { + return false, err + } + + clients, err := s.GetClients(data) + if err != nil { + return false, err + } + + var settings map[string]interface{} + err = json.Unmarshal([]byte(inbound.Settings), &settings) + if err != nil { + return false, err + } + + interfaceClients := settings["clients"].([]interface{}) + + oldClients, err := s.GetClients(inbound) + if err != nil { + return false, err + } + + oldEmail := "" + newClientsubId := "" + clientIndex := -1 + for index, oldClient := range oldClients { + oldClientsubId := "" + if inbound.Protocol == "trojan" { + oldClientsubId = oldClient.SubID + newClientsubId = clients[0].SubID + } else if inbound.Protocol == "shadowsocks" { + oldClientsubId = oldClient.SubID + newClientsubId = clients[0].SubID + } else { + oldClientsubId = oldClient.SubID + newClientsubId = clients[0].SubID + } + if subId == oldClientsubId { + oldEmail = oldClient.Email + clientIndex = index + break + } + } + + if newClientsubId == "" || clientIndex == -1 { + return false, common.NewError("empty client SubID") + } + + if len(clients[0].Email) > 0 && clients[0].Email != oldEmail { + existEmail, err := s.checkEmailsExistForClients(clients) + if err != nil { + return false, err + } + if existEmail != "" { + return false, common.NewError("Duplicate email:", existEmail) + } + } + + var oldSettings map[string]interface{} + err = json.Unmarshal([]byte(inbound.Settings), &oldSettings) + if err != nil { + return false, err + } + settingsClients := oldSettings["clients"].([]interface{}) + settingsClients[clientIndex] = interfaceClients[0] + oldSettings["clients"] = settingsClients + + newSettings, err := json.MarshalIndent(oldSettings, "", " ") + if err != nil { + return false, err + } + + inbound.Settings = string(newSettings) + db := database.GetDB() + tx := db.Begin() + + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + + if len(clients[0].Email) > 0 { + if len(oldEmail) > 0 { + err = s.UpdateClientStat(tx, oldEmail, &clients[0]) + if err != nil { + return false, err + } + err = s.UpdateClientIPs(tx, oldEmail, clients[0].Email) + if err != nil { + return false, err + } + } else { + s.AddClientStat(tx, inbound.Id, &clients[0]) + } + } else { + err = s.DelClientStat(tx, oldEmail) + if err != nil { + return false, err + } + err = s.DelClientIPs(tx, oldEmail) + if err != nil { + return false, err + } + } + + if len(oldEmail) > 0 { + s.xrayApi.Init(p.GetAPIPort()) + err1 := s.xrayApi.RemoveUser(inbound.Tag, oldEmail) + if err1 == nil { + logger.Debug("Old client deleted by api:", clients[0].Email) + } else { + logger.Debug("Error in deleting client by api:", err1) + needRestart = true + } + if clients[0].Enable { + cipher := "" + if inbound.Protocol == "shadowsocks" { + cipher = oldSettings["method"].(string) + } + err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{ + "email": clients[0].Email, + "id": clients[0].ID, + "flow": clients[0].Flow, + "password": clients[0].Password, + "cipher": cipher, + }) + if err1 == nil { + logger.Debug("Client edited by api:", clients[0].Email) + } else { + logger.Debug("Error in adding client by api:", err1) + needRestart = true + } + } + s.xrayApi.Close() + } else { + logger.Debug("Client old email not found") + needRestart = true + } + + if err := tx.Save(inbound).Error; err != nil { + return false, err + } + } + + return needRestart, nil +} + + func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { var err error db := database.GetDB() From 16ed53ef3151bd455e392f65d86d46447dda4701 Mon Sep 17 00:00:00 2001 From: Javad Tinatpak Date: Tue, 6 Aug 2024 16:47:24 +0330 Subject: [PATCH 3/7] fix bug --- web/controller/inbound.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 93b409e2..176d3bf8 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -261,9 +261,7 @@ func (a *InboundController) updateClientInMultipleInbounds(c *gin.Context) { } } - needRestart := true - - needRestart, err = a.inboundService.UpdateClientInMultipleInbounds(data, subId, payload.InboundIds) + needRestart, err := a.inboundService.UpdateClientInMultipleInbounds(data, subId, payload.InboundIds) if err != nil { jsonMsg(c, "Something went wrong!", err) return From f5ce334f75a107d85f4f310198f53d1811b62fa3 Mon Sep 17 00:00:00 2001 From: MrRadikal Date: Tue, 6 Aug 2024 16:57:56 +0330 Subject: [PATCH 4/7] Delete web/controller/inbound.go --- web/controller/inbound.go | 413 -------------------------------------- 1 file changed, 413 deletions(-) delete mode 100644 web/controller/inbound.go diff --git a/web/controller/inbound.go b/web/controller/inbound.go deleted file mode 100644 index 176d3bf8..00000000 --- a/web/controller/inbound.go +++ /dev/null @@ -1,413 +0,0 @@ -package controller - -import ( - "encoding/json" - "fmt" - "strconv" - - "x-ui/database/model" - "x-ui/web/service" - "x-ui/web/session" - - "github.com/gin-gonic/gin" -) - -type InboundController struct { - inboundService service.InboundService - xrayService service.XrayService -} - -type AddClientPayload struct { - Settings string `json:"settings"` - InboundIds []int `json:"inboundIds"` -} - -func NewInboundController(g *gin.RouterGroup) *InboundController { - a := &InboundController{} - a.initRouter(g) - return a -} - -func (a *InboundController) initRouter(g *gin.RouterGroup) { - g = g.Group("/inbound") - - g.POST("/list", a.getInbounds) - g.POST("/add", a.addInbound) - g.POST("/del/:id", a.delInbound) - g.POST("/update/:id", a.updateInbound) - 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("/updateClientInbounds/:subId", a.updateClientInMultipleInbounds) - g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) - g.POST("/resetAllTraffics", a.resetAllTraffics) - g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) - g.POST("/delDepletedClients/:id", a.delDepletedClients) - g.POST("/import", a.importInbound) - g.POST("/onlines", a.onlines) -} - -func (a *InboundController) getInbounds(c *gin.Context) { - user := session.GetLoginUser(c) - inbounds, err := a.inboundService.GetInbounds(user.Id) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err) - return - } - jsonObj(c, inbounds, nil) -} - -func (a *InboundController) getInbound(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "get"), err) - return - } - inbound, err := a.inboundService.GetInbound(id) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err) - return - } - jsonObj(c, inbound, nil) -} - -func (a *InboundController) getClientTraffics(c *gin.Context) { - email := c.Param("email") - clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email) - if err != nil { - jsonMsg(c, "Error getting traffics", err) - return - } - jsonObj(c, clientTraffics, nil) -} - -func (a *InboundController) getClientTrafficsById(c *gin.Context) { - id := c.Param("id") - clientTraffics, err := a.inboundService.GetClientTrafficByID(id) - if err != nil { - jsonMsg(c, "Error getting traffics", err) - return - } - jsonObj(c, clientTraffics, nil) -} - -func (a *InboundController) addInbound(c *gin.Context) { - inbound := &model.Inbound{} - err := c.ShouldBind(inbound) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.create"), err) - return - } - user := session.GetLoginUser(c) - inbound.UserId = user.Id - if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { - inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - } else { - inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) - } - - needRestart := false - inbound, needRestart, err = a.inboundService.AddInbound(inbound) - jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) delInbound(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "delete"), err) - return - } - needRestart := true - needRestart, err = a.inboundService.DelInbound(id) - jsonMsgObj(c, I18nWeb(c, "delete"), id, err) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) updateInbound(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - inbound := &model.Inbound{ - Id: id, - } - err = c.ShouldBind(inbound) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - needRestart := true - inbound, needRestart, err = a.inboundService.UpdateInbound(inbound) - jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) getClientIps(c *gin.Context) { - email := c.Param("email") - - ips, err := a.inboundService.GetInboundClientIps(email) - if err != nil || ips == "" { - jsonObj(c, "No IP Record", nil) - return - } - - jsonObj(c, ips, nil) -} - -func (a *InboundController) clearClientIps(c *gin.Context) { - email := c.Param("email") - - err := a.inboundService.ClearClientIps(email) - if err != nil { - jsonMsg(c, "Update", err) - return - } - jsonMsg(c, "Log Cleared", nil) -} - -func (a *InboundController) addInboundClient(c *gin.Context) { - data := &model.Inbound{} - err := c.ShouldBind(data) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - - needRestart := true - - needRestart, err = a.inboundService.AddInboundClient(data) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "Client(s) added", nil) - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - - -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{ - 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) updateClientInMultipleInbounds(c *gin.Context) { - var payload AddClientPayload - subId := c.Param("subId") - - if err := c.ShouldBindJSON(&payload); err != nil { - jsonMsg(c, "Invalid request payload", err) - return - } - - data := &model.Inbound{ - 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.UpdateClientInMultipleInbounds(data, subId, payload.InboundIds) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "Client updated in 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 { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - clientId := c.Param("clientId") - - needRestart := true - - needRestart, err = a.inboundService.DelInboundClient(id, clientId) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "Client deleted", nil) - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) updateInboundClient(c *gin.Context) { - clientId := c.Param("clientId") - - inbound := &model.Inbound{} - err := c.ShouldBind(inbound) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - - needRestart := true - - needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "Client updated", nil) - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) resetClientTraffic(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - email := c.Param("email") - - needRestart, err := a.inboundService.ResetClientTraffic(id, email) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "Traffic has been reset", nil) - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) resetAllTraffics(c *gin.Context) { - err := a.inboundService.ResetAllTraffics() - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } else { - a.xrayService.SetToNeedRestart() - } - jsonMsg(c, "all traffic has been reset", nil) -} - -func (a *InboundController) resetAllClientTraffics(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - - err = a.inboundService.ResetAllClientTraffics(id) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } else { - a.xrayService.SetToNeedRestart() - } - jsonMsg(c, "All traffic from the client has been reset.", nil) -} - -func (a *InboundController) importInbound(c *gin.Context) { - inbound := &model.Inbound{} - err := json.Unmarshal([]byte(c.PostForm("data")), inbound) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - user := session.GetLoginUser(c) - inbound.Id = 0 - inbound.UserId = user.Id - if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { - inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - } else { - inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) - } - - for index := range inbound.ClientStats { - inbound.ClientStats[index].Id = 0 - inbound.ClientStats[index].Enable = true - } - - needRestart := false - inbound, needRestart, err = a.inboundService.AddInbound(inbound) - jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) delDepletedClients(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - err = a.inboundService.DelDepletedClients(id) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "All depleted clients are deleted", nil) -} - -func (a *InboundController) onlines(c *gin.Context) { - jsonObj(c, a.inboundService.GetOnlineClients(), nil) -} From 58dc0935661aa98454788ad33d6c328fa68b8340 Mon Sep 17 00:00:00 2001 From: MrRadikal Date: Tue, 6 Aug 2024 16:58:19 +0330 Subject: [PATCH 5/7] Delete README.md --- README.md | 535 ------------------------------------------------------ 1 file changed, 535 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 7ae5f374..00000000 --- a/README.md +++ /dev/null @@ -1,535 +0,0 @@ -[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md) - -

Image

- -**An Advanced Web Panel • Built on Xray Core** - -[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) - -> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment - -**If this project is helpful to you, you may wish to give it a**:star2: - -

- - Image - -

- -- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` -- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` -- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - -## Install & Upgrade - -``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) -``` - -## Install Custom Version - -To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.11`: - -``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.11 -``` - -## SSL Certificate - -
- Click for SSL Certificate details - -### ACME - -To manage SSL certificates using ACME: - -1. Ensure your domain is correctly resolved to the server. -2. Run the `x-ui` command in the terminal, then choose `SSL Certificate Management`. -3. You will be presented with the following options: - - - **Get SSL:** Obtain SSL certificates. - - **Revoke:** Revoke existing SSL certificates. - - **Force Renew:** Force renewal of SSL certificates. - -### Certbot - -To install and use Certbot: - -```sh -apt-get install certbot -y -certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com -certbot renew --dry-run -``` - -### Cloudflare - -The management script includes a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following: - -- Cloudflare registered email -- Cloudflare Global API Key -- The domain name must be resolved to the current server through Cloudflare - -**How to get the Cloudflare Global API Key:** - -1. Run the `x-ui` command in the terminal, then choose `Cloudflare SSL Certificate`. -2. Visit the link: [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens). -3. Click on "View Global API Key" (see the screenshot below): - ![](media/APIKey1.PNG) -4. You may need to re-authenticate your account. After that, the API Key will be shown (see the screenshot below): - ![](media/APIKey2.png) - -When using, just enter your `domain name`, `email`, and `API KEY`. The diagram is as follows: - ![](media/DetailEnter.png) - - -
- -## Manual Install & Upgrade - -
- Click for manual install details - -#### Usage - -1. To download the latest version of the compressed package directly to your server, run the following command: - -```sh -ARCH=$(uname -m) -case "${ARCH}" in - x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; - i*86 | x86) XUI_ARCH="386" ;; - armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; - armv7* | armv7) XUI_ARCH="armv7" ;; - armv6* | armv6) XUI_ARCH="armv6" ;; - armv5* | armv5) XUI_ARCH="armv5" ;; - s390x) echo 's390x' ;; - *) XUI_ARCH="amd64" ;; -esac - - -wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz -``` - -2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui: - -```sh -ARCH=$(uname -m) -case "${ARCH}" in - x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; - i*86 | x86) XUI_ARCH="386" ;; - armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; - armv7* | armv7) XUI_ARCH="armv7" ;; - armv6* | armv6) XUI_ARCH="armv6" ;; - armv5* | armv5) XUI_ARCH="armv5" ;; - s390x) echo 's390x' ;; - *) XUI_ARCH="amd64" ;; -esac - -cd /root/ -rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz -chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh -cp x-ui/x-ui.sh /usr/bin/x-ui -cp -f x-ui/x-ui.service /etc/systemd/system/ -mv x-ui/ /usr/local/ -systemctl daemon-reload -systemctl enable x-ui -systemctl restart x-ui -``` - -
- -## Install with Docker - -
- Click for Docker details - -#### Usage - -1. **Install Docker:** - - ```sh - bash <(curl -sSL https://get.docker.com) - ``` - -2. **Clone the Project Repository:** - - ```sh - git clone https://github.com/MHSanaei/3x-ui.git - cd 3x-ui - ``` - -3. **Start the Service:** - - ```sh - docker compose up -d - ``` - - **OR** - - ```sh - docker run -itd \ - -e XRAY_VMESS_AEAD_FORCED=false \ - -v $PWD/db/:/etc/x-ui/ \ - -v $PWD/cert/:/root/cert/ \ - --network=host \ - --restart=unless-stopped \ - --name 3x-ui \ - ghcr.io/mhsanaei/3x-ui:latest - ``` - -4. **Update to the Latest Version:** - - ```sh - cd 3x-ui - docker compose down - docker compose pull 3x-ui - docker compose up -d - ``` - -5. **Remove 3x-ui from Docker:** - - ```sh - docker stop 3x-ui - docker rm 3x-ui - cd -- - rm -r 3x-ui - ``` - -
- - -## Recommended OS - -- Ubuntu 20.04+ -- Debian 11+ -- CentOS 8+ -- Fedora 36+ -- Arch Linux -- Parch Linux -- Manjaro -- Armbian -- AlmaLinux 9+ -- Rocky Linux 9+ -- Oracle Linux 8+ -- OpenSUSE Tubleweed - -## Supported Architectures and Devices - -
- Click for Supported Architectures and devices details - -Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support: - -- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly. - -- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems. - -- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more. - -- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others. - -- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture. - -- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones. - -- **s390x**: This architecture is commonly used in IBM mainframe computers and offers high performance and reliability for enterprise workloads. -
- -## Languages - -- English -- Farsi -- Chinese -- Russian -- Vietnamese -- Spanish -- Indonesian -- Ukrainian - - -## Features - -- System Status Monitoring -- Search within all inbounds and clients -- Dark/Light theme -- Supports multi-user and multi-protocol -- Supports protocols, including VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, Socks, HTTP, wireguard -- Supports XTLS native Protocols, including RPRX-Direct, Vision, REALITY -- Traffic statistics, traffic limit, expiration time limit -- Customizable Xray configuration templates -- Supports HTTPS access panel (self-provided domain name + SSL certificate) -- Supports One-Click SSL certificate application and automatic renewal -- For more advanced configuration items, please refer to the panel -- Fixes API routes (user setting will be created with API) -- Supports changing configs by different items provided in the panel. -- Supports export/import database from the panel - - -## Default Panel Settings - -
- Click for default settings details - -### Username & Password & webbasepath: - - These will be generated randomly if you skip modifying them. - - - **Port:** the default port for panel is `2053` - -### Database Management: - - You can conveniently perform database Backups and Restores directly from the panel. - -- **Database Path:** - - `/etc/x-ui/x-ui.db` - - -### Web Base Path - -1. **Reset Web Base Path:** - - Open your terminal. - - Run the `x-ui` command. - - Select the option to `Reset Web Base Path`. - -2. **Generate or Customize Path:** - - The path will be randomly generated, or you can enter a custom path. - -3. **View Current Settings:** - - To view your current settings, use the `x-ui settings` command in the terminal or `View Current Settings` in `x-ui` - -### Security Recommendation: -- For enhanced security, use a long, random word in your URL structure. - -**Examples:** -- `http://ip:port/*webbasepath*/panel` -- `http://domain:port/*webbasepath*/panel` - -
- -## WARP Configuration - -
- Click for WARP configuration details - -#### Usage - -**For versions `v2.1.0` and later:** - -WARP is built-in, and no additional installation is required. Simply turn on the necessary configuration in the panel. - -**For versions before `v2.1.0`:** - -1. Run the `x-ui` command in the terminal, then choose `WARP Management`. -2. You will see the following options: - - - **Account Type (free, plus, team):** Choose the appropriate account type. - - **Enable/Disable WireProxy:** Toggle WireProxy on or off. - - **Uninstall WARP:** Remove the WARP application. - -3. Configure the settings as needed in the panel. - -
- -## IP Limit - -
- Click for IP limit details - -#### Usage - -**Note:** IP Limit won't work correctly when using IP Tunnel. - -- **For versions up to `v1.6.1`:** - - The IP limit is built-in to the panel - -**For versions `v1.7.0` and newer:** - -To enable the IP Limit functionality, you need to install `fail2ban` and its required files by following these steps: - -1. Run the `x-ui` command in the terminal, then choose `IP Limit Management`. -2. You will see the following options: - - - **Change Ban Duration:** Adjust the duration of bans. - - **Unban Everyone:** Lift all current bans. - - **Check Logs:** Review the logs. - - **Fail2ban Status:** Check the status of `fail2ban`. - - **Restart Fail2ban:** Restart the `fail2ban` service. - - **Uninstall Fail2ban:** Uninstall Fail2ban with configuration. - -3. Add a path for the access log on the panel by setting `Xray Configs/log/Access log` to `./access.log` then save and restart xray. - -- **For versions before `v2.1.3`:** - - You need to set the access log path manually in your Xray configuration: - - ```sh - "log": { - "access": "./access.log", - "dnsLog": false, - "loglevel": "warning" - }, - ``` - -- **For versions `v2.1.3` and newer:** - - There is an option for configuring `access.log` directly from the panel. - -
- -## Telegram Bot - -
- Click for Telegram bot details - -#### Usage - -The web panel supports daily traffic, panel login, database backup, system status, client info, and other notification and functions through the Telegram Bot. To use the bot, you need to set the bot-related parameters in the panel, including: - -- Telegram Token -- Admin Chat ID(s) -- Notification Time (in cron syntax) -- Expiration Date Notification -- Traffic Cap Notification -- Database Backup -- CPU Load Notification - - -**Reference syntax:** - -- `30 \* \* \* \* \*` - Notify at the 30s of each point -- `0 \*/10 \* \* \* \*` - Notify at the first second of each 10 minutes -- `@hourly` - Hourly notification -- `@daily` - Daily notification (00:00 in the morning) -- `@weekly` - weekly notification -- `@every 8h` - Notify every 8 hours - -### Telegram Bot Features - -- Report periodic -- Login notification -- CPU threshold notification -- Threshold for Expiration time and Traffic to report in advance -- Support client report menu if client's telegram username added to the user's configurations -- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously -- Menu based bot -- Search client by email ( only admin ) -- Check all inbounds -- Check server status -- Check depleted users -- Receive backup by request and in periodic reports -- Multi language bot - -### Setting up Telegram bot - -- Start [Botfather](https://t.me/BotFather) in your Telegram account: - ![Botfather](./media/botfather.png) - -- Create a new Bot using /newbot command: It will ask you 2 questions, A name and a username for your bot. Note that the username has to end with the word "bot". - ![Create new bot](./media/newbot.png) - -- Start the bot you've just created. You can find the link to your bot here. - ![token](./media/token.png) - -- Enter your panel and config Telegram bot settings like below: -![Panel Config](./media/panel-bot-config.png) - -Enter your bot token in input field number 3. -Enter the user ID in input field number 4. The Telegram accounts with this id will be the bot admin. (You can enter more than one, Just separate them with ,) - -- How to get Telegram user ID? Use this [bot](https://t.me/useridinfobot), Start the bot and it will give you the Telegram user ID. -![User ID](./media/user-id.png) - -
- -## API Routes - -
- Click for API routes details - -#### Usage - -- `/login` with `POST` user data: `{username: '', password: ''}` for login -- `/panel/api/inbounds` base for following actions: - -| Method | Path | Action | -| :----: | ---------------------------------- | ------------------------------------------- | -| `GET` | `"/list"` | Get all inbounds | -| `GET` | `"/get/:id"` | Get inbound with inbound.id | -| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email | -| `GET` | `"/getClientTrafficsById/:id"` | Get client's traffic By ID | -| `GET` | `"/createbackup"` | Telegram bot sends backup to admins | -| `POST` | `"/add"` | Add inbound | -| `POST` | `"/del/:id"` | Delete Inbound | -| `POST` | `"/update/:id"` | Update Inbound | -| `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` | `"/updateClientInbounds/:subId"` | Update Client by subId\* | -| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic | -| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds | -| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound | -| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) | -| `POST` | `"/onlines"` | Get Online users ( list of emails ) | - -\*- The field `clientId` should be filled by: - -- `client.id` for VMESS and VLESS -- `client.password` for TROJAN -- `client.email` for Shadowsocks - - -- [API Documentation](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm) -- [Run In Postman](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9) -
- -## Environment Variables - -
- Click for environment variables details - -#### Usage - -| Variable | Type | Default | -| -------------- | :--------------------------------------------: | :------------ | -| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | -| XUI_DEBUG | `boolean` | `false` | -| XUI_BIN_FOLDER | `string` | `"bin"` | -| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | -| XUI_LOG_FOLDER | `string` | `"/var/log"` | - -Example: - -```sh -XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go -``` - -
- -## Preview - -![1](./media/1.png) -![2](./media/2.png) -![3](./media/3.png) -![4](./media/4.png) -![5](./media/5.png) -![6](./media/6.png) -![7](./media/7.png) - -## A Special Thanks to - -- [alireza0](https://github.com/alireza0/) - -## Acknowledgment - -- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ -- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ - -## Stargazers over Time - -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) From 82a31b67643f469b6541acceb893d94946083b38 Mon Sep 17 00:00:00 2001 From: MrRadikal Date: Tue, 6 Aug 2024 16:58:55 +0330 Subject: [PATCH 6/7] Delete web/controller/api.go --- web/controller/api.go | 61 ------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 web/controller/api.go diff --git a/web/controller/api.go b/web/controller/api.go deleted file mode 100644 index 0ddb879b..00000000 --- a/web/controller/api.go +++ /dev/null @@ -1,61 +0,0 @@ -package controller - -import ( - "x-ui/web/service" - - "github.com/gin-gonic/gin" -) - -type APIController struct { - BaseController - inboundController *InboundController - Tgbot service.Tgbot -} - -func NewAPIController(g *gin.RouterGroup) *APIController { - a := &APIController{} - a.initRouter(g) - return a -} - -func (a *APIController) initRouter(g *gin.RouterGroup) { - g = g.Group("/panel/api/inbounds") - g.Use(a.checkLogin) - - a.inboundController = NewInboundController(g) - - inboundRoutes := []struct { - Method string - Path string - Handler gin.HandlerFunc - }{ - {"GET", "/createbackup", a.createBackup}, - {"GET", "/list", a.inboundController.getInbounds}, - {"GET", "/get/:id", a.inboundController.getInbound}, - {"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics}, - {"GET", "/getClientTrafficsById/:id", a.inboundController.getClientTrafficsById}, - {"POST", "/add", a.inboundController.addInbound}, - {"POST", "/del/:id", a.inboundController.delInbound}, - {"POST", "/update/:id", a.inboundController.updateInbound}, - {"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", "/updateClientInbounds/:subId", a.inboundController.updateClientInMultipleInbounds}, - {"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic}, - {"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics}, - {"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, - {"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients}, - {"POST", "/onlines", a.inboundController.onlines}, - } - - for _, route := range inboundRoutes { - g.Handle(route.Method, route.Path, route.Handler) - } -} - -func (a *APIController) createBackup(c *gin.Context) { - a.Tgbot.SendBackupToAdmins() -} From 2eaa2098771cc3b3268acd579385adc807d74e34 Mon Sep 17 00:00:00 2001 From: MrRadikal Date: Tue, 6 Aug 2024 16:59:09 +0330 Subject: [PATCH 7/7] Delete web/service/inbound.go --- web/service/inbound.go | 2257 ---------------------------------------- 1 file changed, 2257 deletions(-) delete mode 100644 web/service/inbound.go diff --git a/web/service/inbound.go b/web/service/inbound.go deleted file mode 100644 index bc1a902a..00000000 --- a/web/service/inbound.go +++ /dev/null @@ -1,2257 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "time" - - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - "x-ui/util/common" - "x-ui/xray" - - "gorm.io/gorm" -) - -type InboundService struct { - xrayApi xray.XrayAPI -} - -func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Preload("ClientStats").Where("user_id = ?", userId).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Preload("ClientStats").Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) { - db := database.GetDB() - if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" { - db = db.Model(model.Inbound{}).Where("port = ?", port) - } else { - db = db.Model(model.Inbound{}). - Where("port = ?", port). - Where( - db.Model(model.Inbound{}).Where( - "listen = ?", listen, - ).Or( - "listen = \"\"", - ).Or( - "listen = \"0.0.0.0\"", - ).Or( - "listen = \"::\"", - ).Or( - "listen = \"::0\"")) - } - if ignoreId > 0 { - db = db.Where("id != ?", ignoreId) - } - var count int64 - err := db.Count(&count).Error - if err != nil { - return false, err - } - return count > 0, nil -} - -func (s *InboundService) GetClients(inbound *model.Inbound) ([]model.Client, error) { - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inbound.Settings), &settings) - if settings == nil { - return nil, fmt.Errorf("setting is null") - } - - clients := settings["clients"] - if clients == nil { - return nil, nil - } - return clients, nil -} - -func (s *InboundService) getAllEmails() ([]string, error) { - db := database.GetDB() - var emails []string - err := db.Raw(` - SELECT JSON_EXTRACT(client.value, '$.email') - FROM inbounds, - JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client - `).Scan(&emails).Error - if err != nil { - return nil, err - } - return emails, nil -} - -func (s *InboundService) contains(slice []string, str string) bool { - for _, s := range slice { - if s == str { - return true - } - } - return false -} - -func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) { - allEmails, err := s.getAllEmails() - if err != nil { - return "", err - } - var emails []string - for _, client := range clients { - if client.Email != "" { - if s.contains(emails, client.Email) { - return client.Email, nil - } - if s.contains(allEmails, client.Email) { - return client.Email, nil - } - emails = append(emails, client.Email) - } - } - return "", nil -} - -func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (string, error) { - clients, err := s.GetClients(inbound) - if err != nil { - return "", err - } - allEmails, err := s.getAllEmails() - if err != nil { - return "", err - } - var emails []string - for _, client := range clients { - if client.Email != "" { - if s.contains(emails, client.Email) { - return client.Email, nil - } - if s.contains(allEmails, client.Email) { - return client.Email, nil - } - emails = append(emails, client.Email) - } - } - return "", nil -} - -func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { - exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0) - if err != nil { - return inbound, false, err - } - if exist { - return inbound, false, common.NewError("Port already exists:", inbound.Port) - } - - existEmail, err := s.checkEmailExistForInbound(inbound) - if err != nil { - return inbound, false, err - } - if existEmail != "" { - return inbound, false, common.NewError("Duplicate email:", existEmail) - } - - clients, err := s.GetClients(inbound) - if err != nil { - return inbound, false, err - } - - // Secure client ID - for _, client := range clients { - if inbound.Protocol == "trojan" { - if client.Password == "" { - return inbound, false, common.NewError("empty client ID") - } - } else if inbound.Protocol == "shadowsocks" { - if client.Email == "" { - return inbound, false, common.NewError("empty client ID") - } - } else { - if client.ID == "" { - return inbound, false, common.NewError("empty client ID") - } - } - } - - db := database.GetDB() - tx := db.Begin() - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - err = tx.Save(inbound).Error - if err == nil { - if len(inbound.ClientStats) == 0 { - for _, client := range clients { - s.AddClientStat(tx, inbound.Id, &client) - } - } - } else { - return inbound, false, err - } - - needRestart := false - if inbound.Enable { - s.xrayApi.Init(p.GetAPIPort()) - inboundJson, err1 := json.MarshalIndent(inbound.GenXrayInboundConfig(), "", " ") - if err1 != nil { - logger.Debug("Unable to marshal inbound config:", err1) - } - - err1 = s.xrayApi.AddInbound(inboundJson) - if err1 == nil { - logger.Debug("New inbound added by api:", inbound.Tag) - } else { - logger.Debug("Unable to add inbound by api:", err1) - needRestart = true - } - s.xrayApi.Close() - } - - return inbound, needRestart, err -} - -func (s *InboundService) DelInbound(id int) (bool, error) { - db := database.GetDB() - - var tag string - needRestart := false - result := db.Model(model.Inbound{}).Select("tag").Where("id = ? and enable = ?", id, true).First(&tag) - if result.Error == nil { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.DelInbound(tag) - if err1 == nil { - logger.Debug("Inbound deleted by api:", tag) - } else { - logger.Debug("Unable to delete inbound by api:", err1) - needRestart = true - } - s.xrayApi.Close() - } else { - logger.Debug("No enabled inbound founded to removing by api", tag) - } - - // Delete client traffics of inbounds - err := db.Where("inbound_id = ?", id).Delete(xray.ClientTraffic{}).Error - if err != nil { - return false, err - } - inbound, err := s.GetInbound(id) - if err != nil { - return false, err - } - clients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - for _, client := range clients { - err := s.DelClientIPs(db, client.Email) - if err != nil { - return false, err - } - } - - return needRestart, db.Delete(model.Inbound{}, id).Error -} - -func (s *InboundService) GetInbound(id int) (*model.Inbound, error) { - db := database.GetDB() - inbound := &model.Inbound{} - err := db.Model(model.Inbound{}).First(inbound, id).Error - if err != nil { - return nil, err - } - return inbound, nil -} - -func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { - exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id) - if err != nil { - return inbound, false, err - } - if exist { - return inbound, false, common.NewError("Port already exists:", inbound.Port) - } - - oldInbound, err := s.GetInbound(inbound.Id) - if err != nil { - return inbound, false, err - } - - tag := oldInbound.Tag - - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - err = s.updateClientTraffics(tx, oldInbound, inbound) - if err != nil { - return inbound, false, err - } - - oldInbound.Up = inbound.Up - oldInbound.Down = inbound.Down - oldInbound.Total = inbound.Total - oldInbound.Remark = inbound.Remark - oldInbound.Enable = inbound.Enable - oldInbound.ExpiryTime = inbound.ExpiryTime - oldInbound.Listen = inbound.Listen - oldInbound.Port = inbound.Port - oldInbound.Protocol = inbound.Protocol - oldInbound.Settings = inbound.Settings - oldInbound.StreamSettings = inbound.StreamSettings - oldInbound.Sniffing = inbound.Sniffing - if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { - oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - } else { - oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) - } - - needRestart := false - s.xrayApi.Init(p.GetAPIPort()) - if s.xrayApi.DelInbound(tag) == nil { - logger.Debug("Old inbound deleted by api:", tag) - } - if inbound.Enable { - inboundJson, err2 := json.MarshalIndent(oldInbound.GenXrayInboundConfig(), "", " ") - if err2 != nil { - logger.Debug("Unable to marshal updated inbound config:", err2) - needRestart = true - } else { - err2 = s.xrayApi.AddInbound(inboundJson) - if err2 == nil { - logger.Debug("Updated inbound added by api:", oldInbound.Tag) - } else { - logger.Debug("Unable to update inbound by api:", err2) - needRestart = true - } - } - } - s.xrayApi.Close() - - return inbound, needRestart, tx.Save(oldInbound).Error -} - -func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inbound, newInbound *model.Inbound) error { - oldClients, err := s.GetClients(oldInbound) - if err != nil { - return err - } - newClients, err := s.GetClients(newInbound) - if err != nil { - return err - } - - var emailExists bool - - for _, oldClient := range oldClients { - emailExists = false - for _, newClient := range newClients { - if oldClient.Email == newClient.Email { - emailExists = true - break - } - } - if !emailExists { - err = s.DelClientStat(tx, oldClient.Email) - if err != nil { - return err - } - } - } - for _, newClient := range newClients { - emailExists = false - for _, oldClient := range oldClients { - if newClient.Email == oldClient.Email { - emailExists = true - break - } - } - if !emailExists { - err = s.AddClientStat(tx, oldInbound.Id, &newClient) - if err != nil { - return err - } - } - } - return nil -} - -func (s *InboundService) AddInboundClient(data *model.Inbound) (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{}) - existEmail, err := s.checkEmailsExistForClients(clients) - if err != nil { - return false, err - } - if existEmail != "" { - return false, common.NewError("Duplicate email:", existEmail) - } - - oldInbound, err := s.GetInbound(data.Id) - if err != nil { - return false, err - } - - // Secure client ID - for _, client := range clients { - if oldInbound.Protocol == "trojan" { - if client.Password == "" { - return false, common.NewError("empty client ID") - } - } else if oldInbound.Protocol == "shadowsocks" { - if client.Email == "" { - return false, common.NewError("empty client ID") - } - } else { - if client.ID == "" { - return false, common.NewError("empty client ID") - } - } - } - - 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) - - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - needRestart := false - s.xrayApi.Init(p.GetAPIPort()) - for _, client := range clients { - if len(client.Email) > 0 { - s.AddClientStat(tx, data.Id, &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("Client added by api:", client.Email) - } else { - logger.Debug("Error in adding client by api:", err1) - needRestart = true - } - } - } else { - needRestart = true - } - } - s.xrayApi.Close() - - 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 { - logger.Error("Load Old Data Error") - return false, err - } - var settings map[string]interface{} - err = json.Unmarshal([]byte(oldInbound.Settings), &settings) - if err != nil { - return false, err - } - - email := "" - client_key := "id" - if oldInbound.Protocol == "trojan" { - client_key = "password" - } - if oldInbound.Protocol == "shadowsocks" { - client_key = "email" - } - - interfaceClients := settings["clients"].([]interface{}) - var newClients []interface{} - for _, client := range interfaceClients { - c := client.(map[string]interface{}) - c_id := c[client_key].(string) - if c_id == clientId { - email = c["email"].(string) - } else { - newClients = append(newClients, client) - } - } - - if len(newClients) == 0 { - return false, common.NewError("no client remained in Inbound") - } - - settings["clients"] = newClients - newSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - - oldInbound.Settings = string(newSettings) - - db := database.GetDB() - err = s.DelClientStat(db, email) - if err != nil { - logger.Error("Delete stats Data Error") - return false, err - } - - err = s.DelClientIPs(db, email) - if err != nil { - logger.Error("Error in delete client IPs") - return false, err - } - needRestart := false - if len(email) > 0 { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email) - if err1 == nil { - logger.Debug("Client deleted by api:", email) - needRestart = false - } else { - logger.Debug("Unable to del client by api:", err1) - needRestart = true - } - s.xrayApi.Close() - } - return needRestart, db.Save(oldInbound).Error -} - -func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (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{}) - - oldInbound, err := s.GetInbound(data.Id) - if err != nil { - return false, err - } - - oldClients, err := s.GetClients(oldInbound) - if err != nil { - return false, err - } - - oldEmail := "" - newClientId := "" - clientIndex := -1 - for index, oldClient := range oldClients { - oldClientId := "" - if oldInbound.Protocol == "trojan" { - oldClientId = oldClient.Password - newClientId = clients[0].Password - } else if oldInbound.Protocol == "shadowsocks" { - oldClientId = oldClient.Email - newClientId = clients[0].Email - } else { - oldClientId = oldClient.ID - newClientId = clients[0].ID - } - if clientId == oldClientId { - oldEmail = oldClient.Email - clientIndex = index - break - } - } - - // Validate new client ID - if newClientId == "" || clientIndex == -1 { - return false, common.NewError("empty client ID") - } - - if len(clients[0].Email) > 0 && clients[0].Email != oldEmail { - existEmail, err := s.checkEmailsExistForClients(clients) - if err != nil { - return false, err - } - if existEmail != "" { - return false, common.NewError("Duplicate email:", existEmail) - } - } - - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) - if err != nil { - return false, err - } - settingsClients := oldSettings["clients"].([]interface{}) - settingsClients[clientIndex] = interfaceClients[0] - oldSettings["clients"] = settingsClients - - newSettings, err := json.MarshalIndent(oldSettings, "", " ") - if err != nil { - return false, err - } - - oldInbound.Settings = string(newSettings) - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - if len(clients[0].Email) > 0 { - if len(oldEmail) > 0 { - err = s.UpdateClientStat(tx, oldEmail, &clients[0]) - if err != nil { - return false, err - } - err = s.UpdateClientIPs(tx, oldEmail, clients[0].Email) - if err != nil { - return false, err - } - } else { - s.AddClientStat(tx, data.Id, &clients[0]) - } - } else { - err = s.DelClientStat(tx, oldEmail) - if err != nil { - return false, err - } - err = s.DelClientIPs(tx, oldEmail) - if err != nil { - return false, err - } - } - needRestart := false - if len(oldEmail) > 0 { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail) - if err1 == nil { - logger.Debug("Old client deleted by api:", clients[0].Email) - } else { - logger.Debug("Error in deleting client by api:", err1) - needRestart = true - } - if clients[0].Enable { - cipher := "" - if oldInbound.Protocol == "shadowsocks" { - cipher = oldSettings["method"].(string) - } - err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{ - "email": clients[0].Email, - "id": clients[0].ID, - "flow": clients[0].Flow, - "password": clients[0].Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client edited by api:", clients[0].Email) - } else { - logger.Debug("Error in adding client by api:", err1) - needRestart = true - } - } - s.xrayApi.Close() - } else { - logger.Debug("Client old email not found") - needRestart = true - } - return needRestart, tx.Save(oldInbound).Error -} - -func (s *InboundService) UpdateClientInMultipleInbounds(data *model.Inbound, subId string, inboundIds []int) (bool, error) { - var needRestart bool - - for _, inboundId := range inboundIds { - inbound, err := s.GetInbound(inboundId) - if err != nil { - return false, err - } - - clients, err := s.GetClients(data) - if err != nil { - return false, err - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - - interfaceClients := settings["clients"].([]interface{}) - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - oldEmail := "" - newClientsubId := "" - clientIndex := -1 - for index, oldClient := range oldClients { - oldClientsubId := "" - if inbound.Protocol == "trojan" { - oldClientsubId = oldClient.SubID - newClientsubId = clients[0].SubID - } else if inbound.Protocol == "shadowsocks" { - oldClientsubId = oldClient.SubID - newClientsubId = clients[0].SubID - } else { - oldClientsubId = oldClient.SubID - newClientsubId = clients[0].SubID - } - if subId == oldClientsubId { - oldEmail = oldClient.Email - clientIndex = index - break - } - } - - if newClientsubId == "" || clientIndex == -1 { - return false, common.NewError("empty client SubID") - } - - if len(clients[0].Email) > 0 && clients[0].Email != oldEmail { - existEmail, err := s.checkEmailsExistForClients(clients) - if err != nil { - return false, err - } - if existEmail != "" { - return false, common.NewError("Duplicate email:", existEmail) - } - } - - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &oldSettings) - if err != nil { - return false, err - } - settingsClients := oldSettings["clients"].([]interface{}) - settingsClients[clientIndex] = interfaceClients[0] - oldSettings["clients"] = settingsClients - - newSettings, err := json.MarshalIndent(oldSettings, "", " ") - if err != nil { - return false, err - } - - inbound.Settings = string(newSettings) - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - if len(clients[0].Email) > 0 { - if len(oldEmail) > 0 { - err = s.UpdateClientStat(tx, oldEmail, &clients[0]) - if err != nil { - return false, err - } - err = s.UpdateClientIPs(tx, oldEmail, clients[0].Email) - if err != nil { - return false, err - } - } else { - s.AddClientStat(tx, inbound.Id, &clients[0]) - } - } else { - err = s.DelClientStat(tx, oldEmail) - if err != nil { - return false, err - } - err = s.DelClientIPs(tx, oldEmail) - if err != nil { - return false, err - } - } - - if len(oldEmail) > 0 { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.RemoveUser(inbound.Tag, oldEmail) - if err1 == nil { - logger.Debug("Old client deleted by api:", clients[0].Email) - } else { - logger.Debug("Error in deleting client by api:", err1) - needRestart = true - } - if clients[0].Enable { - cipher := "" - if inbound.Protocol == "shadowsocks" { - cipher = oldSettings["method"].(string) - } - err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{ - "email": clients[0].Email, - "id": clients[0].ID, - "flow": clients[0].Flow, - "password": clients[0].Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client edited by api:", clients[0].Email) - } else { - logger.Debug("Error in adding client by api:", err1) - needRestart = true - } - } - s.xrayApi.Close() - } else { - logger.Debug("Client old email not found") - needRestart = true - } - - if err := tx.Save(inbound).Error; err != nil { - return false, err - } - } - - return needRestart, nil -} - - -func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { - var err error - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - err = s.addInboundTraffic(tx, inboundTraffics) - if err != nil { - return err, false - } - err = s.addClientTraffic(tx, clientTraffics) - if err != nil { - return err, false - } - - needRestart0, count, err := s.autoRenewClients(tx) - if err != nil { - logger.Warning("Error in renew clients:", err) - } else if count > 0 { - logger.Debugf("%v clients renewed", count) - } - - needRestart1, count, err := s.disableInvalidClients(tx) - if err != nil { - logger.Warning("Error in disabling invalid clients:", err) - } else if count > 0 { - logger.Debugf("%v clients disabled", count) - } - - needRestart2, count, err := s.disableInvalidInbounds(tx) - if err != nil { - logger.Warning("Error in disabling invalid inbounds:", err) - } else if count > 0 { - logger.Debugf("%v inbounds disabled", count) - } - return nil, (needRestart0 || needRestart1 || needRestart2) -} - -func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error { - if len(traffics) == 0 { - return nil - } - - var err error - - for _, traffic := range traffics { - if traffic.IsInbound { - err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag). - Updates(map[string]interface{}{ - "up": gorm.Expr("up + ?", traffic.Up), - "down": gorm.Expr("down + ?", traffic.Down), - }).Error - if err != nil { - return err - } - } - } - return nil -} - -func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) { - if len(traffics) == 0 { - // Empty onlineUsers - if p != nil { - p.SetOnlineClients(nil) - } - return nil - } - - var onlineClients []string - - emails := make([]string, 0, len(traffics)) - for _, traffic := range traffics { - emails = append(emails, traffic.Email) - } - dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics)) - err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error - if err != nil { - return err - } - - // Avoid empty slice error - if len(dbClientTraffics) == 0 { - return nil - } - - dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics) - if err != nil { - return err - } - - for dbTraffic_index := range dbClientTraffics { - for traffic_index := range traffics { - if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email { - dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up - dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down - - // Add user in onlineUsers array on traffic - if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 { - onlineClients = append(onlineClients, traffics[traffic_index].Email) - } - break - } - } - } - - // Set onlineUsers - p.SetOnlineClients(onlineClients) - - err = tx.Save(dbClientTraffics).Error - if err != nil { - logger.Warning("AddClientTraffic update data ", err) - } - - return nil -} - -func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) { - inboundIds := make([]int, 0, len(dbClientTraffics)) - for _, dbClientTraffic := range dbClientTraffics { - if dbClientTraffic.ExpiryTime < 0 { - inboundIds = append(inboundIds, dbClientTraffic.InboundId) - } - } - - if len(inboundIds) > 0 { - var inbounds []*model.Inbound - err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error - if err != nil { - return nil, err - } - for inbound_index := range inbounds { - settings := map[string]interface{}{} - json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) - clients, ok := settings["clients"].([]interface{}) - if ok { - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - for traffic_index := range dbClientTraffics { - if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email { - oldExpiryTime := c["expiryTime"].(float64) - newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime) - c["expiryTime"] = newExpiryTime - dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime - break - } - } - newClients = append(newClients, interface{}(c)) - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return nil, err - } - - inbounds[inbound_index].Settings = string(modifiedSettings) - } - } - err = tx.Save(inbounds).Error - if err != nil { - logger.Warning("AddClientTraffic update inbounds ", err) - logger.Error(inbounds) - } - } - - return dbClientTraffics, nil -} - -func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) { - // check for time expired - var traffics []*xray.ClientTraffic - now := time.Now().Unix() * 1000 - var err, err1 error - - err = tx.Model(xray.ClientTraffic{}).Where("reset > 0 and expiry_time > 0 and expiry_time <= ?", now).Find(&traffics).Error - if err != nil { - return false, 0, err - } - // return if there is no client to renew - if len(traffics) == 0 { - return false, 0, nil - } - - var inbound_ids []int - var inbounds []*model.Inbound - needRestart := false - var clientsToAdd []struct { - protocol string - tag string - client map[string]interface{} - } - - for _, traffic := range traffics { - inbound_ids = append(inbound_ids, traffic.InboundId) - } - err = tx.Model(model.Inbound{}).Where("id IN ?", inbound_ids).Find(&inbounds).Error - if err != nil { - return false, 0, err - } - for inbound_index := range inbounds { - settings := map[string]interface{}{} - json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) - clients := settings["clients"].([]interface{}) - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - for traffic_index, traffic := range traffics { - if traffic.Email == c["email"].(string) { - newExpiryTime := traffic.ExpiryTime - for newExpiryTime < now { - newExpiryTime += (int64(traffic.Reset) * 86400000) - } - c["expiryTime"] = newExpiryTime - traffics[traffic_index].ExpiryTime = newExpiryTime - traffics[traffic_index].Down = 0 - traffics[traffic_index].Up = 0 - if !traffic.Enable { - traffics[traffic_index].Enable = true - clientsToAdd = append(clientsToAdd, - struct { - protocol string - tag string - client map[string]interface{} - }{ - protocol: string(inbounds[inbound_index].Protocol), - tag: inbounds[inbound_index].Tag, - client: c, - }) - } - clients[client_index] = interface{}(c) - break - } - } - } - settings["clients"] = clients - newSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, 0, err - } - inbounds[inbound_index].Settings = string(newSettings) - } - err = tx.Save(inbounds).Error - if err != nil { - return false, 0, err - } - err = tx.Save(traffics).Error - if err != nil { - return false, 0, err - } - if p != nil { - err1 = s.xrayApi.Init(p.GetAPIPort()) - if err1 != nil { - return true, int64(len(traffics)), nil - } - for _, clientToAdd := range clientsToAdd { - err1 = s.xrayApi.AddUser(clientToAdd.protocol, clientToAdd.tag, clientToAdd.client) - if err1 != nil { - needRestart = true - } - } - s.xrayApi.Close() - } - return needRestart, int64(len(traffics)), nil -} - -func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) { - now := time.Now().Unix() * 1000 - needRestart := false - - if p != nil { - var tags []string - err := tx.Table("inbounds"). - Select("inbounds.tag"). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Scan(&tags).Error - if err != nil { - return false, 0, err - } - s.xrayApi.Init(p.GetAPIPort()) - for _, tag := range tags { - err1 := s.xrayApi.DelInbound(tag) - if err1 == nil { - logger.Debug("Inbound disabled by api:", tag) - } else { - logger.Debug("Error in disabling inbound by api:", err1) - needRestart = true - } - } - s.xrayApi.Close() - } - - result := tx.Model(model.Inbound{}). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Update("enable", false) - err := result.Error - count := result.RowsAffected - return needRestart, count, err -} - -func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) { - now := time.Now().Unix() * 1000 - needRestart := false - - if p != nil { - var results []struct { - Tag string - Email string - } - - err := tx.Table("inbounds"). - Select("inbounds.tag, client_traffics.email"). - Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id"). - Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true). - Scan(&results).Error - if err != nil { - return false, 0, err - } - s.xrayApi.Init(p.GetAPIPort()) - for _, result := range results { - err1 := s.xrayApi.RemoveUser(result.Tag, result.Email) - if err1 == nil { - logger.Debug("Client disabled by api:", result.Email) - } else { - logger.Debug("Error in disabling client by api:", err1) - needRestart = true - } - } - s.xrayApi.Close() - } - result := tx.Model(xray.ClientTraffic{}). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Update("enable", false) - err := result.Error - count := result.RowsAffected - return needRestart, count, err -} - -func (s *InboundService) GetInboundTags() (string, error) { - db := database.GetDB() - var inboundTags []string - err := db.Model(model.Inbound{}).Select("tag").Find(&inboundTags).Error - if err != nil && err != gorm.ErrRecordNotFound { - return "", err - } - tags, _ := json.Marshal(inboundTags) - return string(tags), nil -} - -func (s *InboundService) MigrationRemoveOrphanedTraffics() { - db := database.GetDB() - db.Exec(` - DELETE FROM client_traffics - WHERE email NOT IN ( - SELECT JSON_EXTRACT(client.value, '$.email') - FROM inbounds, - JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client - ) - `) -} - -func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model.Client) error { - clientTraffic := xray.ClientTraffic{} - clientTraffic.InboundId = inboundId - clientTraffic.Email = client.Email - clientTraffic.Total = client.TotalGB - clientTraffic.ExpiryTime = client.ExpiryTime - clientTraffic.Enable = true - clientTraffic.Up = 0 - clientTraffic.Down = 0 - clientTraffic.Reset = client.Reset - result := tx.Create(&clientTraffic) - err := result.Error - return err -} - -func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error { - result := tx.Model(xray.ClientTraffic{}). - Where("email = ?", email). - Updates(map[string]interface{}{ - "enable": true, - "email": client.Email, - "total": client.TotalGB, - "expiry_time": client.ExpiryTime, - "reset": client.Reset, - }) - err := result.Error - return err -} - -func (s *InboundService) UpdateClientIPs(tx *gorm.DB, oldEmail string, newEmail string) error { - return tx.Model(model.InboundClientIps{}).Where("client_email = ?", oldEmail).Update("client_email", newEmail).Error -} - -func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error { - return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error -} - -func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error { - return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error -} - -func (s *InboundService) GetClientInboundByTrafficID(trafficId int) (traffic *xray.ClientTraffic, inbound *model.Inbound, err error) { - db := database.GetDB() - var traffics []*xray.ClientTraffic - err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error - if err != nil { - logger.Warningf("Error retrieving ClientTraffic with trafficId %d: %v", trafficId, err) - return nil, nil, err - } - if len(traffics) > 0 { - inbound, err = s.GetInbound(traffics[0].InboundId) - return traffics[0], inbound, err - } - return nil, nil, nil -} - -func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.ClientTraffic, inbound *model.Inbound, err error) { - db := database.GetDB() - var traffics []*xray.ClientTraffic - err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error - if err != nil { - logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err) - return nil, nil, err - } - if len(traffics) > 0 { - inbound, err = s.GetInbound(traffics[0].InboundId) - return traffics[0], inbound, err - } - return nil, nil, nil -} - -func (s *InboundService) GetClientByEmail(clientEmail string) (*xray.ClientTraffic, *model.Client, error) { - traffic, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return nil, nil, err - } - if inbound == nil { - return nil, nil, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - clients, err := s.GetClients(inbound) - if err != nil { - return nil, nil, err - } - - for _, client := range clients { - if client.Email == clientEmail { - return traffic, &client, nil - } - } - - return nil, nil, common.NewError("Client Not Found In Inbound For Email:", clientEmail) -} - -func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (bool, error) { - traffic, inbound, err := s.GetClientInboundByTrafficID(trafficId) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Traffic ID:", trafficId) - } - - clientEmail := traffic.Email - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["tgId"] = tgId - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - clients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - isEnable := false - - for _, client := range clients { - if client.Email == clientEmail { - isEnable = client.Enable - break - } - } - - return isEnable, err -} - -func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, false, err - } - if inbound == nil { - return false, false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, false, err - } - - clientId := "" - clientOldEnabled := false - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - clientOldEnabled = oldClient.Enable - break - } - } - - if len(clientId) == 0 { - return false, false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["enable"] = !clientOldEnabled - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, false, err - } - inbound.Settings = string(modifiedSettings) - - needRestart, err := s.UpdateInboundClient(inbound, clientId) - if err != nil { - return false, needRestart, err - } - - return !clientOldEnabled, needRestart, nil -} - -func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["limitIp"] = count - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) (bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["expiryTime"] = expiry_time - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, totalGB int) (bool, error) { - if totalGB < 0 { - return false, common.NewError("totalGB must be >= 0") - } - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["totalGB"] = totalGB * 1024 * 1024 * 1024 - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error { - db := database.GetDB() - - result := db.Model(xray.ClientTraffic{}). - Where("email = ?", clientEmail). - Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0}) - - err := result.Error - if err != nil { - return err - } - return nil -} - -func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, error) { - needRestart := false - - traffic, err := s.GetClientTrafficByEmail(clientEmail) - if err != nil { - return false, err - } - - if !traffic.Enable { - inbound, err := s.GetInbound(id) - if err != nil { - return false, err - } - clients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - for _, client := range clients { - if client.Email == clientEmail { - s.xrayApi.Init(p.GetAPIPort()) - cipher := "" - if string(inbound.Protocol) == "shadowsocks" { - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &oldSettings) - if err != nil { - return false, err - } - cipher = oldSettings["method"].(string) - } - err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{ - "email": client.Email, - "id": client.ID, - "flow": client.Flow, - "password": client.Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client enabled due to reset traffic:", clientEmail) - } else { - logger.Debug("Error in enabling client by api:", err1) - needRestart = true - } - s.xrayApi.Close() - break - } - } - } - - traffic.Up = 0 - traffic.Down = 0 - traffic.Enable = true - - db := database.GetDB() - err = db.Save(traffic).Error - if err != nil { - return false, err - } - - return needRestart, nil -} - -func (s *InboundService) ResetAllClientTraffics(id int) error { - db := database.GetDB() - - whereText := "inbound_id " - if id == -1 { - whereText += " > ?" - } else { - whereText += " = ?" - } - - result := db.Model(xray.ClientTraffic{}). - Where(whereText, id). - Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0}) - - err := result.Error - return err -} - -func (s *InboundService) ResetAllTraffics() error { - db := database.GetDB() - - result := db.Model(model.Inbound{}). - Where("user_id > ?", 0). - Updates(map[string]interface{}{"up": 0, "down": 0}) - - err := result.Error - return err -} - -func (s *InboundService) DelDepletedClients(id int) (err error) { - db := database.GetDB() - tx := db.Begin() - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - whereText := "reset = 0 and inbound_id " - if id < 0 { - whereText += "> ?" - } else { - whereText += "= ?" - } - - depletedClients := []xray.ClientTraffic{} - err = db.Model(xray.ClientTraffic{}).Where(whereText+" and enable = ?", id, false).Select("inbound_id, GROUP_CONCAT(email) as email").Group("inbound_id").Find(&depletedClients).Error - if err != nil { - return err - } - - for _, depletedClient := range depletedClients { - emails := strings.Split(depletedClient.Email, ",") - oldInbound, err := s.GetInbound(depletedClient.InboundId) - if err != nil { - return err - } - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) - if err != nil { - return err - } - - oldClients := oldSettings["clients"].([]interface{}) - var newClients []interface{} - for _, client := range oldClients { - deplete := false - c := client.(map[string]interface{}) - for _, email := range emails { - if email == c["email"].(string) { - deplete = true - break - } - } - if !deplete { - newClients = append(newClients, client) - } - } - if len(newClients) > 0 { - oldSettings["clients"] = newClients - - newSettings, err := json.MarshalIndent(oldSettings, "", " ") - if err != nil { - return err - } - - oldInbound.Settings = string(newSettings) - err = tx.Save(oldInbound).Error - if err != nil { - return err - } - } else { - // Delete inbound if no client remains - s.DelInbound(depletedClient.InboundId) - } - } - - err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error - if err != nil { - return err - } - - return nil -} - -func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) { - db := database.GetDB() - var inbounds []*model.Inbound - - // Retrieve inbounds where settings contain the given tgId - err := db.Model(model.Inbound{}).Where("settings LIKE ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err) - return nil, err - } - - var emails []string - for _, inbound := range inbounds { - clients, err := s.GetClients(inbound) - if err != nil { - logger.Errorf("Error retrieving clients for inbound %d: %v", inbound.Id, err) - continue - } - for _, client := range clients { - if client.TgID == tgId { - emails = append(emails, client.Email) - } - } - } - - var traffics []*xray.ClientTraffic - err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - logger.Warning("No ClientTraffic records found for emails:", emails) - return nil, nil - } - logger.Errorf("Error retrieving ClientTraffic for emails %v: %v", emails, err) - return nil, err - } - - return traffics, nil -} - -func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) { - db := database.GetDB() - var traffics []*xray.ClientTraffic - - err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error - if err != nil { - logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err) - return nil, err - } - if len(traffics) > 0 { - return traffics[0], nil - } - - return nil, nil -} - -func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic, error) { - db := database.GetDB() - var traffics []xray.ClientTraffic - - err := db.Model(xray.ClientTraffic{}).Where(`email IN( - SELECT JSON_EXTRACT(client.value, '$.email') as email - FROM inbounds, - JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client - WHERE - JSON_EXTRACT(client.value, '$.id') in (?) - )`, id).Find(&traffics).Error - - if err != nil { - logger.Debug(err) - return nil, err - } - return traffics, err -} - -func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) { - db := database.GetDB() - inbound := &model.Inbound{} - traffic = &xray.ClientTraffic{} - - // Search for inbound settings that contain the query - err = db.Model(model.Inbound{}).Where("settings LIKE ?", "%\""+query+"\"%").First(inbound).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - logger.Warningf("Inbound settings containing query %s not found: %v", query, err) - return nil, err - } - logger.Errorf("Error searching for inbound settings with query %s: %v", query, err) - return nil, err - } - - traffic.InboundId = inbound.Id - - // Unmarshal settings to get clients - settings := map[string][]model.Client{} - if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil { - logger.Errorf("Error unmarshalling inbound settings for inbound ID %d: %v", inbound.Id, err) - return nil, err - } - - clients := settings["clients"] - for _, client := range clients { - if (client.ID == query || client.Password == query) && client.Email != "" { - traffic.Email = client.Email - break - } - } - - if traffic.Email == "" { - logger.Warningf("No client found with query %s in inbound ID %d", query, inbound.Id) - return nil, gorm.ErrRecordNotFound - } - - // Retrieve ClientTraffic based on the found email - err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - logger.Warningf("ClientTraffic for email %s not found: %v", traffic.Email, err) - return nil, err - } - logger.Errorf("Error retrieving ClientTraffic for email %s: %v", traffic.Email, err) - return nil, err - } - - return traffic, nil -} - -func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) { - db := database.GetDB() - InboundClientIps := &model.InboundClientIps{} - err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error - if err != nil { - return "", err - } - return InboundClientIps.Ips, nil -} - -func (s *InboundService) ClearClientIps(clientEmail string) error { - db := database.GetDB() - - result := db.Model(model.InboundClientIps{}). - Where("client_email = ?", clientEmail). - Update("ips", "") - err := result.Error - if err != nil { - return err - } - return nil -} - -func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Preload("ClientStats").Where("remark like ?", "%"+query+"%").Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) MigrationRequirements() { - db := database.GetDB() - tx := db.Begin() - var err error - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - // Fix inbounds based problems - var inbounds []*model.Inbound - err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return - } - for inbound_index := range inbounds { - settings := map[string]interface{}{} - json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) - clients, ok := settings["clients"].([]interface{}) - if ok { - // Fix Client configuration problems - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - - // Add email='' if it is not exists - if _, ok := c["email"]; !ok { - c["email"] = "" - } - - // Convert string tgId to int64 - if _, ok := c["tgId"]; ok { - var tgId interface{} = c["tgId"] - if tgIdStr, ok2 := tgId.(string); ok2 { - tgIdInt64, err := strconv.ParseInt(strings.ReplaceAll(tgIdStr, " ", ""), 10, 64) - if err == nil { - c["tgId"] = tgIdInt64 - } - } - } - - // Remove "flow": "xtls-rprx-direct" - if _, ok := c["flow"]; ok { - if c["flow"] == "xtls-rprx-direct" { - c["flow"] = "" - } - } - newClients = append(newClients, interface{}(c)) - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return - } - - inbounds[inbound_index].Settings = string(modifiedSettings) - } - - // Add client traffic row for all clients which has email - modelClients, err := s.GetClients(inbounds[inbound_index]) - if err != nil { - return - } - for _, modelClient := range modelClients { - if len(modelClient.Email) > 0 { - var count int64 - tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count) - if count == 0 { - s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient) - } - } - } - } - tx.Save(inbounds) - - // Remove orphaned traffics - tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{}) - - // Migrate old MultiDomain to External Proxy - var externalProxy []struct { - Id int - Port int - StreamSettings []byte - } - err = tx.Raw(`select id, port, stream_settings - from inbounds - WHERE protocol in ('vmess','vless','trojan') - AND json_extract(stream_settings, '$.security') = 'tls' - AND json_extract(stream_settings, '$.tlsSettings.settings.domains') IS NOT NULL`).Scan(&externalProxy).Error - if err != nil || len(externalProxy) == 0 { - return - } - - for _, ep := range externalProxy { - var reverses interface{} - var stream map[string]interface{} - json.Unmarshal(ep.StreamSettings, &stream) - if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok { - if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok { - if domains, ok := settings["domains"].([]interface{}); ok { - for _, domain := range domains { - if domainMap, ok := domain.(map[string]interface{}); ok { - domainMap["forceTls"] = "same" - domainMap["port"] = ep.Port - domainMap["dest"] = domainMap["domain"].(string) - delete(domainMap, "domain") - } - } - } - reverses = settings["domains"] - delete(settings, "domains") - } - } - stream["externalProxy"] = reverses - newStream, _ := json.MarshalIndent(stream, " ", " ") - tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream) - } - - err = tx.Raw(`UPDATE inbounds - SET tag = REPLACE(tag, '0.0.0.0:', '') - WHERE INSTR(tag, '0.0.0.0:') > 0;`).Error - if err != nil { - return - } -} - -func (s *InboundService) MigrateDB() { - s.MigrationRequirements() - s.MigrationRemoveOrphanedTraffics() -} - -func (s *InboundService) GetOnlineClients() []string { - return p.GetOnlineClients() -}