push config
This commit is contained in:
@@ -9,8 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.rosy.net.cn/jx-callback/globals"
|
|
||||||
|
|
||||||
"git.rosy.net.cn/baseapi/utils"
|
"git.rosy.net.cn/baseapi/utils"
|
||||||
"git.rosy.net.cn/jx-callback/business/model"
|
"git.rosy.net.cn/jx-callback/business/model"
|
||||||
"git.rosy.net.cn/jx-callback/business/model/dao"
|
"git.rosy.net.cn/jx-callback/business/model/dao"
|
||||||
@@ -311,7 +309,6 @@ func LoginInternal(ctx *Context, authType, authID, authIDType, authSecret string
|
|||||||
if appID == model.JXC4ClientAppID && authInfo.AuthBindInfo.UserID != "" {
|
if appID == model.JXC4ClientAppID && authInfo.AuthBindInfo.UserID != "" {
|
||||||
binds, err := dao.GetUserBindAuthInfo(dao.GetDB(), authInfo.AuthBindInfo.UserID, 0, nil, "", "", []string{model.JXC4ClientAppID})
|
binds, err := dao.GetUserBindAuthInfo(dao.GetDB(), authInfo.AuthBindInfo.UserID, 0, nil, "", "", []string{model.JXC4ClientAppID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
globals.SugarLogger.Debugf("AuthTypeWXApp authInfo=%s,err=%v", utils.Format4Output(authInfo, false), err)
|
|
||||||
return authInfo, err
|
return authInfo, err
|
||||||
}
|
}
|
||||||
if len(binds) == 0 {
|
if len(binds) == 0 {
|
||||||
@@ -327,7 +324,6 @@ func LoginInternal(ctx *Context, authType, authID, authIDType, authSecret string
|
|||||||
} else {
|
} else {
|
||||||
err = ErrIllegalAuthType
|
err = ErrIllegalAuthType
|
||||||
}
|
}
|
||||||
globals.SugarLogger.Debugf("LoginInternal authInfo=%s", utils.Format4Output(authInfo, false))
|
|
||||||
return authInfo, err
|
return authInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.rosy.net.cn/jx-callback/globals"
|
|
||||||
|
|
||||||
"git.rosy.net.cn/baseapi/utils"
|
"git.rosy.net.cn/baseapi/utils"
|
||||||
"git.rosy.net.cn/jx-callback/business/auth2"
|
"git.rosy.net.cn/jx-callback/business/auth2"
|
||||||
"git.rosy.net.cn/jx-callback/business/model"
|
"git.rosy.net.cn/jx-callback/business/model"
|
||||||
@@ -80,24 +78,6 @@ func (a *DefAuther) UnionFindAuthBind(curAuthType, curAuthTypeID string, unionAu
|
|||||||
} else if dao.IsNoRowsError(err) { // 直接找不到,尝试unionID
|
} else if dao.IsNoRowsError(err) { // 直接找不到,尝试unionID
|
||||||
if unionID != "" || openID != "" { // 且有unionID
|
if unionID != "" || openID != "" { // 且有unionID
|
||||||
var authBindList []*model.AuthBind
|
var authBindList []*model.AuthBind
|
||||||
//authBindList, err = dao.GetUserBindAuthInfo(db, "", model.AuthBindTypeAuth, unionAuthTypeList, openID, unionID, nil)
|
|
||||||
//if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
//}
|
|
||||||
//if len(authBindList) > 0 { // 通过unionID找到至少一个认证方式
|
|
||||||
// authBind = authBindList[0]
|
|
||||||
// authBind.Type = curAuthType
|
|
||||||
// authBind.TypeID = curAuthTypeID
|
|
||||||
// authBind.AuthID = openID
|
|
||||||
// if authDetail != nil {
|
|
||||||
// authBind.DetailData = string(utils.MustMarshal(authDetail))
|
|
||||||
// }
|
|
||||||
// authBindEx = &auth2.AuthBindEx{
|
|
||||||
// AuthBind: *authBind,
|
|
||||||
// }
|
|
||||||
// a.UnbindAuth(authBind.UserID, curAuthType, curAuthTypeID, model.AdminName)
|
|
||||||
// err = a.AddAuthBind(authBindEx, model.AdminName) // 自动绑定
|
|
||||||
//}
|
|
||||||
if authBindList, err = dao.GetUserBindAuthInfo(db, "", model.AuthBindTypeAuth, unionAuthTypeList, openID, unionID, nil); err == nil && len(authBindList) > 0 { // 通过unionID找到至少一个认证方式
|
if authBindList, err = dao.GetUserBindAuthInfo(db, "", model.AuthBindTypeAuth, unionAuthTypeList, openID, unionID, nil); err == nil && len(authBindList) > 0 { // 通过unionID找到至少一个认证方式
|
||||||
authBind = authBindList[0]
|
authBind = authBindList[0]
|
||||||
authBind.Type = curAuthType
|
authBind.Type = curAuthType
|
||||||
@@ -112,18 +92,14 @@ func (a *DefAuther) UnionFindAuthBind(curAuthType, curAuthTypeID string, unionAu
|
|||||||
a.UnbindAuth(authBind.UserID, curAuthType, curAuthTypeID, model.AdminName)
|
a.UnbindAuth(authBind.UserID, curAuthType, curAuthTypeID, model.AdminName)
|
||||||
err = a.AddAuthBind(authBindEx, model.AdminName) // 自动绑定
|
err = a.AddAuthBind(authBindEx, model.AdminName) // 自动绑定
|
||||||
} else if dao.IsNoRowsError(err) {
|
} else if dao.IsNoRowsError(err) {
|
||||||
globals.SugarLogger.Debugf("dao.IsNoRowsError(err)=%s,err=%v", dao.IsNoRowsError(err), err)
|
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globals.SugarLogger.Debugf("authBindList err=%v", err)
|
|
||||||
globals.SugarLogger.Debugf("authBindList authBindEx=%v", err)
|
|
||||||
if err == nil && authBindEx == nil {
|
if err == nil && authBindEx == nil {
|
||||||
//如果没有报错,且没有找到一个认证方式,创建无用户(UserID为空)的认证方式
|
//如果没有报错,且没有找到一个认证方式,创建无用户(UserID为空)的认证方式
|
||||||
globals.SugarLogger.Debugf("进入添加无用户认证方式")
|
|
||||||
authBindEx = &auth2.AuthBindEx{
|
authBindEx = &auth2.AuthBindEx{
|
||||||
AuthBind: model.AuthBind{
|
AuthBind: model.AuthBind{
|
||||||
Type: curAuthType,
|
Type: curAuthType,
|
||||||
@@ -135,8 +111,6 @@ func (a *DefAuther) UnionFindAuthBind(curAuthType, curAuthTypeID string, unionAu
|
|||||||
if authDetail != nil {
|
if authDetail != nil {
|
||||||
authBindEx.DetailData = string(utils.MustMarshal(authDetail))
|
authBindEx.DetailData = string(utils.MustMarshal(authDetail))
|
||||||
}
|
}
|
||||||
globals.SugarLogger.Debugf("authBindEx=%s", utils.Format4Output(authBindEx, false))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return authBindEx, err
|
return authBindEx, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ package weixin
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.rosy.net.cn/baseapi/utils"
|
|
||||||
"git.rosy.net.cn/jx-callback/globals"
|
|
||||||
|
|
||||||
"git.rosy.net.cn/baseapi/platformapi/weixinapi"
|
"git.rosy.net.cn/baseapi/platformapi/weixinapi"
|
||||||
"git.rosy.net.cn/jx-callback/business/auth2"
|
"git.rosy.net.cn/jx-callback/business/auth2"
|
||||||
"git.rosy.net.cn/jx-callback/business/auth2/authprovider"
|
"git.rosy.net.cn/jx-callback/business/auth2/authprovider"
|
||||||
@@ -88,7 +85,6 @@ func (a *Auther) VerifySecret(id, secret string) (authBindEx *auth2.AuthBindEx,
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
wxUserinfo, err2 := a.getAPI().SNSGetUserInfo(accessToken, openID)
|
wxUserinfo, err2 := a.getAPI().SNSGetUserInfo(accessToken, openID)
|
||||||
globals.SugarLogger.Debugf("wxUserinfo=%s", utils.Format4Output(wxUserinfo, false))
|
|
||||||
if err = err2; err == nil {
|
if err = err2; err == nil {
|
||||||
if authBindEx, err = a.UnionFindAuthBind(a.authType, a.getAPI().GetAppID(), []string{AuthTypeWeixin, AuthTypeMP, AuthTypeMini, AuthTypeWXNative, AuthTypeWxApp, AuthTypeWxAppCaishi}, wxUserinfo.OpenID, wxUserinfo.UnionID, wxUserinfo); err == nil {
|
if authBindEx, err = a.UnionFindAuthBind(a.authType, a.getAPI().GetAppID(), []string{AuthTypeWeixin, AuthTypeMP, AuthTypeMini, AuthTypeWXNative, AuthTypeWxApp, AuthTypeWxAppCaishi}, wxUserinfo.OpenID, wxUserinfo.UnionID, wxUserinfo); err == nil {
|
||||||
authBindEx.UserHint = &auth2.UserBasic{
|
authBindEx.UserHint = &auth2.UserBasic{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.rosy.net.cn/jx-callback/business/authz/autils"
|
"git.rosy.net.cn/jx-callback/business/authz/autils"
|
||||||
@@ -21,6 +22,9 @@ const (
|
|||||||
SoundsFileNewAfsOrder = "afsOrder.caf"
|
SoundsFileNewAfsOrder = "afsOrder.caf"
|
||||||
SoundsFileNewCancelOrder = "cancelOrder.caf"
|
SoundsFileNewCancelOrder = "cancelOrder.caf"
|
||||||
SoundsFileNewImMsg = "newMsg.caf"
|
SoundsFileNewImMsg = "newMsg.caf"
|
||||||
|
|
||||||
|
FlagOrder = 1 //订单渠道
|
||||||
|
FlagIm = 2 //消息类型
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotifyNewOrder 推送新订单
|
// NotifyNewOrder 推送新订单
|
||||||
@@ -50,7 +54,7 @@ func NotifyNewOrder(order *model.GoodsOrder) {
|
|||||||
msg.VendorOrderId = order.VendorOrderID
|
msg.VendorOrderId = order.VendorOrderID
|
||||||
context, _ := json.Marshal(msg)
|
context, _ := json.Marshal(msg)
|
||||||
body := fmt.Sprintf(msg.Context+"(%s)", model.VendorChineseNames[order.VendorID]+"#"+msg.OrderSqs)
|
body := fmt.Sprintf(msg.Context+"(%s)", model.VendorChineseNames[order.VendorID]+"#"+msg.OrderSqs)
|
||||||
pushMsgByUniApp(storeDetail.ID, storeDetail.Name, cid, string(context), body, SoundsFileNewOrder)
|
pushMsgByUniApp(storeDetail.ID, storeDetail.Name, cid, string(context), body, SoundsFileNewOrder, FlagOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotifyAfsOrder(afsOrder *model.AfsOrder) (err error) {
|
func NotifyAfsOrder(afsOrder *model.AfsOrder) (err error) {
|
||||||
@@ -78,7 +82,7 @@ func NotifyAfsOrder(afsOrder *model.AfsOrder) (err error) {
|
|||||||
msg.Context = "老板订单申请退款了!"
|
msg.Context = "老板订单申请退款了!"
|
||||||
context, _ := json.Marshal(msg)
|
context, _ := json.Marshal(msg)
|
||||||
body := fmt.Sprintf(msg.Context+"(%s)", model.VendorChineseNames[afsOrder.VendorID]+"#"+msg.OrderSqs)
|
body := fmt.Sprintf(msg.Context+"(%s)", model.VendorChineseNames[afsOrder.VendorID]+"#"+msg.OrderSqs)
|
||||||
pushMsgByUniApp(storeDetail.ID, storeDetail.Name, cid, string(context), body, SoundsFileNewAfsOrder)
|
pushMsgByUniApp(storeDetail.ID, storeDetail.Name, cid, string(context), body, SoundsFileNewAfsOrder, FlagOrder)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +111,7 @@ func NotifyOrderCanceled(order *model.GoodsOrder) (err error) {
|
|||||||
msg.Context = "老板订单被取消了!"
|
msg.Context = "老板订单被取消了!"
|
||||||
context, _ := json.Marshal(msg)
|
context, _ := json.Marshal(msg)
|
||||||
body := fmt.Sprintf(msg.Context+"(%s)", model.VendorChineseNames[order.VendorID]+"#"+msg.OrderSqs)
|
body := fmt.Sprintf(msg.Context+"(%s)", model.VendorChineseNames[order.VendorID]+"#"+msg.OrderSqs)
|
||||||
pushMsgByUniApp(storeDetail.ID, storeDetail.Name, cid, string(context), body, SoundsFileNewCancelOrder)
|
pushMsgByUniApp(storeDetail.ID, storeDetail.Name, cid, string(context), body, SoundsFileNewCancelOrder, FlagOrder)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +139,7 @@ func NotifyImNewMessage(vendorStoreID string, vendorID int) error {
|
|||||||
msg.Context = "老板,你有新的用户消息,请及时查看!"
|
msg.Context = "老板,你有新的用户消息,请及时查看!"
|
||||||
context, _ := json.Marshal(msg)
|
context, _ := json.Marshal(msg)
|
||||||
|
|
||||||
pushMsgByUniApp(store.ID, store.Name, cid, string(context), "", SoundsFileNewImMsg)
|
pushMsgByUniApp(store.ID, store.Name, cid, string(context), "", SoundsFileNewImMsg, FlagIm)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +179,42 @@ type MsgContext struct {
|
|||||||
VendorOrderId string `json:"vendor_order_id"` // 订单id
|
VendorOrderId string `json:"vendor_order_id"` // 订单id
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushMsgByUniApp(storeId int, storeName string, cID []string, msg string, body string, soundsFileName string) {
|
func pushMsgByUniApp(storeId int, storeName string, cID []string, msg string, body string, soundsFileName string, flag int) {
|
||||||
var errs []error
|
var (
|
||||||
|
errs []error
|
||||||
|
options = make(map[string]interface{}, 0)
|
||||||
|
)
|
||||||
|
if flag == FlagOrder {
|
||||||
|
options = map[string]interface{}{
|
||||||
|
"XM": map[string]interface{}{
|
||||||
|
"/extra.channel_id": "108892", // 订单类填写:108892,消息通知类填写:108898
|
||||||
|
},
|
||||||
|
"HW": map[string]interface{}{
|
||||||
|
"/message/android/category": "WORK", // 订单类填写:WORK,消息通知类填写:IM
|
||||||
|
},
|
||||||
|
"OP": map[string]interface{}{
|
||||||
|
"/channel_id": "10110", // 订单类填写:10110,消息通知类填写:10111
|
||||||
|
},
|
||||||
|
"VV": map[string]interface{}{
|
||||||
|
"/category": "ORDER", // 订单类填写:ORDER,消息通知类填写:IM
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if flag == FlagIm {
|
||||||
|
options = map[string]interface{}{
|
||||||
|
"XM": map[string]interface{}{
|
||||||
|
"/extra.channel_id": "108898", // 订单类填写:108892,消息通知类填写:108898
|
||||||
|
},
|
||||||
|
"HW": map[string]interface{}{
|
||||||
|
"/message/android/category": "IM", // 订单类填写:WORK,消息通知类填写:IM
|
||||||
|
},
|
||||||
|
"OP": map[string]interface{}{
|
||||||
|
"/channel_id": "10111", // 订单类填写:10110,消息通知类填写:10111
|
||||||
|
},
|
||||||
|
"VV": map[string]interface{}{
|
||||||
|
"/category": "IM", // 订单类填写:ORDER,消息通知类填写:IM
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, v := range cID {
|
for _, v := range cID {
|
||||||
param := map[string]interface{}{
|
param := map[string]interface{}{
|
||||||
"request_id": utils.Int64ToStr(time.Now().Unix()) + "_" + utils.Int2Str(storeId),
|
"request_id": utils.Int64ToStr(time.Now().Unix()) + "_" + utils.Int2Str(storeId),
|
||||||
@@ -192,7 +230,13 @@ func pushMsgByUniApp(storeId int, storeName string, cID []string, msg string, bo
|
|||||||
"push_channel": map[string]interface{}{
|
"push_channel": map[string]interface{}{
|
||||||
"android": map[string]interface{}{
|
"android": map[string]interface{}{
|
||||||
"ups": map[string]interface{}{
|
"ups": map[string]interface{}{
|
||||||
"transmission": msg,
|
"notification": map[string]interface{}{
|
||||||
|
"title": storeName,
|
||||||
|
"body": msg,
|
||||||
|
"click_type": "startapp",
|
||||||
|
"notify_id": rand.Int(), // 每次通知需要不一样 范围:0-2147483647
|
||||||
|
},
|
||||||
|
"options": options,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ios": map[string]interface{}{
|
"ios": map[string]interface{}{
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"git.rosy.net.cn/baseapi/utils"
|
"git.rosy.net.cn/baseapi/utils"
|
||||||
"git.rosy.net.cn/jx-callback/business/model"
|
"git.rosy.net.cn/jx-callback/business/model"
|
||||||
"git.rosy.net.cn/jx-callback/globals"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAuthBind(db *DaoDB, bindType int, authType, authID string) (authBind *model.AuthBind, err error) {
|
func GetAuthBind(db *DaoDB, bindType int, authType, authID string) (authBind *model.AuthBind, err error) {
|
||||||
@@ -62,7 +61,6 @@ func GetUserBindAuthInfo(db *DaoDB, userID string, bindType int, typeList []stri
|
|||||||
sqlParams = append(sqlParams, typeIDs)
|
sqlParams = append(sqlParams, typeIDs)
|
||||||
}
|
}
|
||||||
sql += " ORDER BY t1.type"
|
sql += " ORDER BY t1.type"
|
||||||
globals.SugarLogger.Debugf("GetUserBindAuthInfo sql=%s,sqlParams=%s", sql, utils.Format4Output(sqlParams, false))
|
|
||||||
err = GetRows(db, &authList, sql, sqlParams...)
|
err = GetRows(db, &authList, sql, sqlParams...)
|
||||||
return authList, err
|
return authList, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package im
|
package im
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.rosy.net.cn/jx-callback/business/model"
|
"git.rosy.net.cn/jx-callback/business/model"
|
||||||
|
|
||||||
@@ -110,6 +112,9 @@ func ReadMsgFromVendor(vendorID int, elmAppID string, msg []byte) error {
|
|||||||
if vendorID == VendorIDMT {
|
if vendorID == VendorIDMT {
|
||||||
var PushContentReq = mtwmapi.PushContentReq{}
|
var PushContentReq = mtwmapi.PushContentReq{}
|
||||||
err = json.Unmarshal(msg, &PushContentReq)
|
err = json.Unmarshal(msg, &PushContentReq)
|
||||||
|
if FilterIm(PushContentReq.AppID, PushContentReq.MsgContent) { //自动回复消息过滤
|
||||||
|
return nil
|
||||||
|
}
|
||||||
jxMsg = &JXMsg{
|
jxMsg = &JXMsg{
|
||||||
SendType: SendTypeMt,
|
SendType: SendTypeMt,
|
||||||
MsgContent: PushContentReq,
|
MsgContent: PushContentReq,
|
||||||
@@ -343,3 +348,30 @@ func DelRedisByKey(keys []string) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rel = map[int]string{
|
||||||
|
589: "a81eb3df418d83d6a1a4b7c572156d2f",
|
||||||
|
5873: "41c479790a76f86326f89e8048964739",
|
||||||
|
4123: "df2c88338b85f830cebce2a9eab56628",
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptIm 解密操作
|
||||||
|
func DecryptIm(appID int, msg string) (string, error) {
|
||||||
|
data, _ := base64.StdEncoding.DecodeString(msg)
|
||||||
|
key := utils.LimitUTF8StringLen2(rel[appID], 16)
|
||||||
|
res, err := utils.AESCBC16Decrypt(data, []byte(key), []byte(key))
|
||||||
|
if len(string(res)) > 0 && err == nil {
|
||||||
|
return string(res), nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterIm 过滤操作
|
||||||
|
func FilterIm(appID int, msg string) bool {
|
||||||
|
var check = "[自动回复]"
|
||||||
|
data, _ := DecryptIm(appID, msg)
|
||||||
|
if len(data) > 0 && strings.Contains(data, check) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.rosy.net.cn/jx-callback/business/jxstore/cms"
|
"git.rosy.net.cn/jx-callback/business/jxstore/cms"
|
||||||
"git.rosy.net.cn/jx-callback/globals"
|
|
||||||
|
|
||||||
"git.rosy.net.cn/jx-callback/globals/api"
|
"git.rosy.net.cn/jx-callback/globals/api"
|
||||||
|
|
||||||
@@ -158,9 +157,7 @@ func (c *Auth2Controller) Login() {
|
|||||||
if dao.IsNoRowsError(err) {
|
if dao.IsNoRowsError(err) {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
globals.SugarLogger.Debugf("SetUserCId err=%v", err)
|
|
||||||
}
|
}
|
||||||
globals.SugarLogger.Debugf("Login retVal=%s,err=%v", utils.Format4Output(retVal, false), err)
|
|
||||||
return retVal, "", err
|
return retVal, "", err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user