cleanup
This commit is contained in:
+32
-52
@@ -13,6 +13,14 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/wyze"
|
"github.com/AlexxIT/go2rtc/pkg/wyze"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AccountConfig struct {
|
||||||
|
APIKey string `yaml:"api_key"`
|
||||||
|
APIID string `yaml:"api_id"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var accounts map[string]AccountConfig
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
var v struct {
|
var v struct {
|
||||||
Cfg map[string]AccountConfig `yaml:"wyze"`
|
Cfg map[string]AccountConfig `yaml:"wyze"`
|
||||||
@@ -31,27 +39,18 @@ func Init() {
|
|||||||
api.HandleFunc("api/wyze", apiWyze)
|
api.HandleFunc("api/wyze", apiWyze)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountConfig struct {
|
|
||||||
APIKey string `yaml:"api_key"`
|
|
||||||
APIID string `yaml:"api_id"`
|
|
||||||
Password string `yaml:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var accounts map[string]AccountConfig
|
|
||||||
|
|
||||||
func getCloud(email string) (*wyze.Cloud, error) {
|
func getCloud(email string) (*wyze.Cloud, error) {
|
||||||
cfg, ok := accounts[email]
|
cfg, ok := accounts[email]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("wyze: account not found: %s", email)
|
return nil, fmt.Errorf("wyze: account not found: %s", email)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cloud *wyze.Cloud
|
if cfg.APIKey == "" || cfg.APIID == "" {
|
||||||
if cfg.APIKey != "" && cfg.APIID != "" {
|
return nil, fmt.Errorf("wyze: api_key and api_id required for account: %s", email)
|
||||||
cloud = wyze.NewCloudWithAPIKey(cfg.APIKey, cfg.APIID)
|
|
||||||
} else {
|
|
||||||
cloud = wyze.NewCloud()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloud := wyze.NewCloud(cfg.APIKey, cfg.APIID)
|
||||||
|
|
||||||
if err := cloud.Login(email, cfg.Password); err != nil {
|
if err := cloud.Login(email, cfg.Password); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -73,7 +72,6 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
email := query.Get("id")
|
email := query.Get("id")
|
||||||
if email == "" {
|
if email == "" {
|
||||||
// Return list of configured accounts
|
|
||||||
accountList := make([]string, 0, len(accounts))
|
accountList := make([]string, 0, len(accounts))
|
||||||
for id := range accounts {
|
for id := range accounts {
|
||||||
accountList = append(accountList, id)
|
accountList = append(accountList, id)
|
||||||
@@ -99,7 +97,7 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
items = append(items, &api.Source{
|
items = append(items, &api.Source{
|
||||||
Name: cam.Nickname,
|
Name: cam.Nickname,
|
||||||
Info: fmt.Sprintf("%s | %s | %s", cam.ModelName(), cam.MAC, cam.IP),
|
Info: fmt.Sprintf("%s | %s | %s", cam.ProductModel, cam.MAC, cam.IP),
|
||||||
URL: streamURL,
|
URL: streamURL,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -113,25 +111,6 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildStreamURL(cam *wyze.Camera) string {
|
|
||||||
// Use IP if available, otherwise use P2P_ID as host
|
|
||||||
host := cam.IP
|
|
||||||
if host == "" {
|
|
||||||
host = cam.P2PID
|
|
||||||
}
|
|
||||||
|
|
||||||
query := url.Values{}
|
|
||||||
query.Set("uid", cam.P2PID)
|
|
||||||
query.Set("enr", cam.ENR)
|
|
||||||
query.Set("mac", cam.MAC)
|
|
||||||
|
|
||||||
if cam.DTLS == 1 {
|
|
||||||
query.Set("dtls", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("wyze://%s?%s", host, query.Encode())
|
|
||||||
}
|
|
||||||
|
|
||||||
func apiAuth(w http.ResponseWriter, r *http.Request) {
|
func apiAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
@@ -143,18 +122,13 @@ func apiAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
apiKey := r.Form.Get("api_key")
|
apiKey := r.Form.Get("api_key")
|
||||||
apiID := r.Form.Get("api_id")
|
apiID := r.Form.Get("api_id")
|
||||||
|
|
||||||
if email == "" || password == "" {
|
if email == "" || password == "" || apiKey == "" || apiID == "" {
|
||||||
http.Error(w, "email and password required", http.StatusBadRequest)
|
http.Error(w, "email, password, api_key and api_id required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to login
|
// Try to login
|
||||||
var cloud *wyze.Cloud
|
cloud := wyze.NewCloud(apiKey, apiID)
|
||||||
if apiKey != "" && apiID != "" {
|
|
||||||
cloud = wyze.NewCloudWithAPIKey(apiKey, apiID)
|
|
||||||
} else {
|
|
||||||
cloud = wyze.NewCloud()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cloud.Login(email, password); err != nil {
|
if err := cloud.Login(email, password); err != nil {
|
||||||
// Check for MFA error
|
// Check for MFA error
|
||||||
@@ -169,15 +143,10 @@ func apiAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save credentials to config (not tokens!)
|
|
||||||
cfg := map[string]string{
|
cfg := map[string]string{
|
||||||
"password": password,
|
"password": password,
|
||||||
}
|
"api_key": apiKey,
|
||||||
if apiKey != "" {
|
"api_id": apiID,
|
||||||
cfg["api_key"] = apiKey
|
|
||||||
}
|
|
||||||
if apiID != "" {
|
|
||||||
cfg["api_id"] = apiID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.PatchConfig([]string{"wyze", email}, cfg); err != nil {
|
if err := app.PatchConfig([]string{"wyze", email}, cfg); err != nil {
|
||||||
@@ -185,7 +154,6 @@ func apiAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update in-memory config
|
|
||||||
if accounts == nil {
|
if accounts == nil {
|
||||||
accounts = make(map[string]AccountConfig)
|
accounts = make(map[string]AccountConfig)
|
||||||
}
|
}
|
||||||
@@ -195,7 +163,6 @@ func apiAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
Password: password,
|
Password: password,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return camera list with direct URLs
|
|
||||||
cameras, err := cloud.GetCameraList()
|
cameras, err := cloud.GetCameraList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
@@ -208,7 +175,7 @@ func apiAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
items = append(items, &api.Source{
|
items = append(items, &api.Source{
|
||||||
Name: cam.Nickname,
|
Name: cam.Nickname,
|
||||||
Info: fmt.Sprintf("%s | %s | %s", cam.ModelName(), cam.MAC, cam.IP),
|
Info: fmt.Sprintf("%s | %s | %s", cam.ProductModel, cam.MAC, cam.IP),
|
||||||
URL: streamURL,
|
URL: streamURL,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -216,6 +183,19 @@ func apiAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
api.ResponseSources(w, items)
|
api.ResponseSources(w, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildStreamURL(cam *wyze.Camera) string {
|
||||||
|
query := url.Values{}
|
||||||
|
query.Set("uid", cam.P2PID)
|
||||||
|
query.Set("enr", cam.ENR)
|
||||||
|
query.Set("mac", cam.MAC)
|
||||||
|
|
||||||
|
if cam.DTLS == 1 {
|
||||||
|
query.Set("dtls", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("wyze://%s?%s", cam.IP, query.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
func isAuthError(err error, target **wyze.AuthError) bool {
|
func isAuthError(err error, target **wyze.AuthError) bool {
|
||||||
if e, ok := err.(*wyze.AuthError); ok {
|
if e, ok := err.(*wyze.AuthError); ok {
|
||||||
*target = e
|
*target = e
|
||||||
|
|||||||
@@ -438,9 +438,6 @@ func (c *Client) buildK10002(challenge []byte, status byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) buildK10010(mediaType byte, enabled bool) []byte {
|
func (c *Client) buildK10010(mediaType byte, enabled bool) []byte {
|
||||||
// SDK format: 18 bytes total
|
|
||||||
// Header: 16 bytes, Payload: 2 bytes (media_type + enabled)
|
|
||||||
// TX K10010: 48 4c 05 00 1a 27 02 00 00 00 00 00 00 00 00 00 01 01
|
|
||||||
buf := make([]byte, 18)
|
buf := make([]byte, 18)
|
||||||
buf[0] = 'H'
|
buf[0] = 'H'
|
||||||
buf[1] = 'L'
|
buf[1] = 'L'
|
||||||
@@ -457,9 +454,6 @@ func (c *Client) buildK10010(mediaType byte, enabled bool) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) buildK10056(frameSize uint8, bitrate uint16) []byte {
|
func (c *Client) buildK10056(frameSize uint8, bitrate uint16) []byte {
|
||||||
// SDK format: 21 bytes total
|
|
||||||
// Header: 16 bytes, Payload: 5 bytes
|
|
||||||
// TX K10056: 48 4c 05 00 48 27 05 00 00 00 00 00 00 00 00 00 04 f0 00 00 00
|
|
||||||
buf := make([]byte, 21)
|
buf := make([]byte, 21)
|
||||||
buf[0] = 'H'
|
buf[0] = 'H'
|
||||||
buf[1] = 'L'
|
buf[1] = 'L'
|
||||||
|
|||||||
+65
-121
@@ -26,9 +26,7 @@ type Cloud struct {
|
|||||||
apiKey string
|
apiKey string
|
||||||
keyID string
|
keyID string
|
||||||
accessToken string
|
accessToken string
|
||||||
refreshToken string
|
|
||||||
phoneID string
|
phoneID string
|
||||||
openUserID string
|
|
||||||
cameras []*Camera
|
cameras []*Camera
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,46 +43,36 @@ type Camera struct {
|
|||||||
IsOnline bool `json:"is_online"`
|
IsOnline bool `json:"is_online"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Camera) ModelName() string {
|
type deviceListResponse struct {
|
||||||
models := map[string]string{
|
Code string `json:"code"`
|
||||||
"WYZEC1": "Wyze Cam v1",
|
Msg string `json:"msg"`
|
||||||
"WYZEC1-JZ": "Wyze Cam v2",
|
Data struct {
|
||||||
"WYZE_CAKP2JFUS": "Wyze Cam v3",
|
DeviceList []deviceInfo `json:"device_list"`
|
||||||
"HL_CAM3P": "Wyze Cam v3 Pro",
|
} `json:"data"`
|
||||||
"HL_CAM4": "Wyze Cam v4",
|
|
||||||
"WYZECP1_JEF": "Wyze Cam Pan",
|
|
||||||
"HL_PANP": "Wyze Cam Pan v2",
|
|
||||||
"HL_PAN3": "Wyze Cam Pan v3",
|
|
||||||
"WVOD1": "Wyze Video Doorbell",
|
|
||||||
"WVOD2": "Wyze Video Doorbell v2",
|
|
||||||
"AN_RSCW": "Wyze Video Doorbell Pro",
|
|
||||||
"GW_BE1": "Wyze Cam Floodlight",
|
|
||||||
"HL_WCO2": "Wyze Cam Outdoor",
|
|
||||||
"HL_CFL2": "Wyze Cam Floodlight v2",
|
|
||||||
"LD_CFP": "Wyze Battery Cam Pro",
|
|
||||||
}
|
|
||||||
if name, ok := models[c.ProductModel]; ok {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return c.ProductModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCloud() *Cloud {
|
type deviceInfo struct {
|
||||||
return &Cloud{
|
MAC string `json:"mac"`
|
||||||
client: &http.Client{Timeout: 30 * time.Second},
|
ENR string `json:"enr"`
|
||||||
phoneID: generatePhoneID(),
|
Nickname string `json:"nickname"`
|
||||||
}
|
ProductModel string `json:"product_model"`
|
||||||
|
ProductType string `json:"product_type"`
|
||||||
|
FirmwareVer string `json:"firmware_ver"`
|
||||||
|
ConnState int `json:"conn_state"`
|
||||||
|
DeviceParams deviceParams `json:"device_params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCloudWithAPIKey(apiKey, keyID string) *Cloud {
|
type deviceParams struct {
|
||||||
c := NewCloud()
|
P2PID string `json:"p2p_id"`
|
||||||
c.apiKey = apiKey
|
P2PType int `json:"p2p_type"`
|
||||||
c.keyID = keyID
|
IP string `json:"ip"`
|
||||||
return c
|
DTLS int `json:"dtls"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func generatePhoneID() string {
|
type p2pInfoResponse struct {
|
||||||
return core.RandString(16, 16) // 16 hex chars
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data map[string]any `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type loginResponse struct {
|
type loginResponse struct {
|
||||||
@@ -96,35 +84,13 @@ type loginResponse struct {
|
|||||||
EmailSessionID string `json:"email_session_id"`
|
EmailSessionID string `json:"email_session_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiError struct {
|
func NewCloud(apiKey, keyID string) *Cloud {
|
||||||
Code string `json:"code"`
|
return &Cloud{
|
||||||
ErrorCode int `json:"errorCode"`
|
client: &http.Client{Timeout: 30 * time.Second},
|
||||||
Msg string `json:"msg"`
|
phoneID: generatePhoneID(),
|
||||||
Description string `json:"description"`
|
apiKey: apiKey,
|
||||||
}
|
keyID: keyID,
|
||||||
|
|
||||||
func (e *apiError) hasError() bool {
|
|
||||||
if e.Code == "1" || e.Code == "0" {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
if e.Code == "" && e.ErrorCode == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return e.Code != "" || e.ErrorCode != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *apiError) message() string {
|
|
||||||
if e.Msg != "" {
|
|
||||||
return e.Msg
|
|
||||||
}
|
|
||||||
return e.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *apiError) code() string {
|
|
||||||
if e.Code != "" {
|
|
||||||
return e.Code
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d", e.ErrorCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cloud) Login(email, password string) error {
|
func (c *Cloud) Login(email, password string) error {
|
||||||
@@ -141,15 +107,9 @@ func (c *Cloud) Login(email, password string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
if c.apiKey != "" && c.keyID != "" {
|
|
||||||
req.Header.Set("Apikey", c.apiKey)
|
req.Header.Set("Apikey", c.apiKey)
|
||||||
req.Header.Set("Keyid", c.keyID)
|
req.Header.Set("Keyid", c.keyID)
|
||||||
req.Header.Set("User-Agent", "go2rtc")
|
req.Header.Set("User-Agent", "go2rtc")
|
||||||
} else {
|
|
||||||
req.Header.Set("X-API-Key", "WMXHYf79Nr5gIlt3r0r7p9Tcw5bvs6BB4U8O8nGJ")
|
|
||||||
req.Header.Set("Phone-Id", c.phoneID)
|
|
||||||
req.Header.Set("User-Agent", "wyze_ios_"+appVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -186,55 +146,10 @@ func (c *Cloud) Login(email, password string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.accessToken = result.AccessToken
|
c.accessToken = result.AccessToken
|
||||||
c.refreshToken = result.RefreshToken
|
|
||||||
c.openUserID = result.UserID
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cloud) LoginWithToken(accessToken, phoneID string) error {
|
|
||||||
c.accessToken = accessToken
|
|
||||||
if phoneID != "" {
|
|
||||||
c.phoneID = phoneID
|
|
||||||
}
|
|
||||||
_, err := c.GetCameraList()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cloud) Credentials() (phoneID, openUserID string) {
|
|
||||||
return c.phoneID, c.openUserID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cloud) AccessToken() string {
|
|
||||||
return c.accessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
type deviceListResponse struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Msg string `json:"msg"`
|
|
||||||
Data struct {
|
|
||||||
DeviceList []deviceInfo `json:"device_list"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type deviceInfo struct {
|
|
||||||
MAC string `json:"mac"`
|
|
||||||
ENR string `json:"enr"`
|
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
ProductModel string `json:"product_model"`
|
|
||||||
ProductType string `json:"product_type"`
|
|
||||||
FirmwareVer string `json:"firmware_ver"`
|
|
||||||
ConnState int `json:"conn_state"`
|
|
||||||
DeviceParams deviceParams `json:"device_params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type deviceParams struct {
|
|
||||||
P2PID string `json:"p2p_id"`
|
|
||||||
P2PType int `json:"p2p_type"`
|
|
||||||
IP string `json:"ip"`
|
|
||||||
DTLS int `json:"dtls"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cloud) GetCameraList() ([]*Camera, error) {
|
func (c *Cloud) GetCameraList() ([]*Camera, error) {
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"access_token": c.accessToken,
|
"access_token": c.accessToken,
|
||||||
@@ -316,12 +231,6 @@ func (c *Cloud) GetCamera(id string) (*Camera, error) {
|
|||||||
return nil, fmt.Errorf("wyze: camera not found: %s", id)
|
return nil, fmt.Errorf("wyze: camera not found: %s", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
type p2pInfoResponse struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Msg string `json:"msg"`
|
|
||||||
Data map[string]any `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cloud) GetP2PInfo(mac string) (map[string]any, error) {
|
func (c *Cloud) GetP2PInfo(mac string) (map[string]any, error) {
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"access_token": c.accessToken,
|
"access_token": c.accessToken,
|
||||||
@@ -367,6 +276,37 @@ func (c *Cloud) GetP2PInfo(mac string) (map[string]any, error) {
|
|||||||
return result.Data, nil
|
return result.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type apiError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
ErrorCode int `json:"errorCode"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *apiError) hasError() bool {
|
||||||
|
if e.Code == "1" || e.Code == "0" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e.Code == "" && e.ErrorCode == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return e.Code != "" || e.ErrorCode != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *apiError) message() string {
|
||||||
|
if e.Msg != "" {
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
return e.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *apiError) code() string {
|
||||||
|
if e.Code != "" {
|
||||||
|
return e.Code
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", e.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
type AuthError struct {
|
type AuthError struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
NeedsMFA bool `json:"needs_mfa,omitempty"`
|
NeedsMFA bool `json:"needs_mfa,omitempty"`
|
||||||
@@ -377,6 +317,10 @@ func (e *AuthError) Error() string {
|
|||||||
return e.Message
|
return e.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generatePhoneID() string {
|
||||||
|
return core.RandString(16, 16) // 16 hex chars
|
||||||
|
}
|
||||||
|
|
||||||
func hashPassword(password string) string {
|
func hashPassword(password string) string {
|
||||||
encoded := strings.TrimSpace(password)
|
encoded := strings.TrimSpace(password)
|
||||||
if strings.HasPrefix(strings.ToLower(encoded), "md5:") {
|
if strings.HasPrefix(strings.ToLower(encoded), "md5:") {
|
||||||
|
|||||||
Reference in New Issue
Block a user