auto renew feature #1286

This commit is contained in:
Alireza Ahmadi 2023-12-04 19:20:16 +01:00
parent 729d8549e2
commit a8b7063647
6 changed files with 162 additions and 27 deletions

View File

@ -83,4 +83,5 @@ type Client struct {
Enable bool `json:"enable" form:"enable"`
TgID string `json:"tgId" form:"tgId"`
SubID string `json:"subId" form:"subId"`
Reset int `json:"reset" form:"reset"`
}

View File

@ -1783,7 +1783,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
}
};
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.id = id;
this.email = email;
@ -1793,6 +1793,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
static fromJson(json={}) {
@ -1805,6 +1806,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}
get _expiryTime() {
@ -1873,7 +1875,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
};
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.id = id;
this.flow = flow;
@ -1884,6 +1886,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
static fromJson(json={}) {
@ -1897,6 +1900,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}
@ -1996,7 +2000,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
}
};
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.password = password;
this.flow = flow;
@ -2007,6 +2011,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
toJson() {
@ -2020,6 +2025,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable: this.enable,
tgId: this.tgId,
subId: this.subId,
reset: this.reset,
};
}
@ -2034,6 +2040,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}
@ -2138,7 +2145,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
};
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super();
this.method = method;
this.password = password;
@ -2149,6 +2156,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.reset = reset;
}
toJson() {
@ -2162,6 +2170,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
enable: this.enable,
tgId: this.tgId,
subId: this.subId,
reset: this.reset,
};
}
@ -2176,6 +2185,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.reset,
);
}

View File

@ -117,6 +117,18 @@
:dropdown-class-name="themeSwitcher.darkCardClass"
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
</a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
<span slot="label">
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</span>
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
</a-form-item>
</a-form>
</a-modal>
<script>
@ -142,6 +154,7 @@
tgId: "",
flow: "",
delayedStart: false,
reset: 0,
ok() {
clients = [];
method = clientsBulkModal.emailMethod;
@ -170,6 +183,7 @@
if (clientsBulkModal.inbound.xtls) {
newClient.flow = clientsBulkModal.flow;
}
newClient.reset = clientsBulkModal.reset;
clients.push(newClient);
}
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
@ -199,6 +213,7 @@
this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound();
this.delayedStart = false;
this.reset = 0;
},
newClient(protocol) {
switch (protocol) {

View File

@ -115,6 +115,12 @@
get statsColor() {
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
},
get delayedStart() {
return this.clientModal.delayedStart;
},
set delayedStart(value) {
this.clientModal.delayedStart = value;
},
get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
},

View File

@ -263,7 +263,18 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
tag := oldInbound.Tag
err = s.updateClientTraffics(oldInbound, inbound)
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
}
@ -304,11 +315,10 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
}
s.xrayApi.Close()
db := database.GetDB()
return inbound, needRestart, db.Save(oldInbound).Error
return inbound, needRestart, tx.Save(oldInbound).Error
}
func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) 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
@ -318,17 +328,6 @@ func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbo
return err
}
db := database.GetDB()
tx := db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
var emailExists bool
for _, oldClient := range oldClients {
@ -601,7 +600,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
if len(clients[0].Email) > 0 {
if len(oldEmail) > 0 {
err = s.UpdateClientStat(oldEmail, &clients[0])
err = s.UpdateClientStat(tx, oldEmail, &clients[0])
if err != nil {
return false, err
}
@ -676,6 +675,13 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
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)
@ -689,7 +695,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
} else if count > 0 {
logger.Debugf("%v inbounds disabled", count)
}
return nil, (needRestart1 || needRestart2)
return nil, (needRestart0 || needRestart1 || needRestart2)
}
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
@ -823,6 +829,102 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
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
@ -916,6 +1018,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
clientTraffic.Enable = true
clientTraffic.Up = 0
clientTraffic.Down = 0
clientTraffic.Reset = client.Reset
result := tx.Create(&clientTraffic)
err := result.Error
if err != nil {
@ -924,16 +1027,15 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
return nil
}
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
db := database.GetDB()
result := db.Model(xray.ClientTraffic{}).
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})
"expiry_time": client.ExpiryTime,
"reset": client.Reset})
err := result.Error
if err != nil {
return err
@ -1429,7 +1531,7 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
}
}()
whereText := "inbound_id "
whereText := "reset = 0 and inbound_id "
if id < 0 {
whereText += "> ?"
} else {

View File

@ -9,4 +9,5 @@ type ClientTraffic struct {
Down int64 `json:"down" form:"down"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
Total int64 `json:"total" form:"total"`
Reset int `json:"reset" form:"reset" gorm:"default:0"`
}