contact rewiring
This commit is contained in:
parent
77a76b6bdb
commit
659cdce84c
10
lib.go
10
lib.go
|
@ -193,12 +193,6 @@ func GetProfiles() string {
|
|||
return string(jsonBytes)
|
||||
}
|
||||
|
||||
type Contact struct {
|
||||
Name string `json:"name"`
|
||||
Onion string `json:"onion"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
//export c_GetContacts
|
||||
func c_GetContacts(onion_ptr *C.char, onion_len C.int) *C.char {
|
||||
return C.CString(GetContacts(C.GoStringN(onion_ptr, onion_len)))
|
||||
|
@ -211,11 +205,11 @@ func GetContacts(onion string) string {
|
|||
contactEventsQueue = event.NewQueue()
|
||||
application.GetEventBus(onion).Subscribe(event.PeerStateChange, contactEventsQueue)
|
||||
|
||||
var contacts []Contact
|
||||
var contacts []utils.Contact
|
||||
for _, contact := range mypeer.GetContacts() {
|
||||
contactInfo := mypeer.GetContact(contact)
|
||||
log.Infof("contactInfo %v", contactInfo)
|
||||
contacts = append(contacts, Contact{Name: contactInfo.Name, Onion: contactInfo.Onion, Status: contactInfo.State})
|
||||
contacts = append(contacts, utils.Contact{Name: contactInfo.Name, Onion: contactInfo.Onion, Status: contactInfo.State})
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(contacts)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package utils
|
||||
|
||||
type Contact struct {
|
||||
Name string `json:"name"`
|
||||
Onion string `json:"onion"`
|
||||
Status string `json:"status"`
|
||||
Picture string `json:"picture"`
|
||||
}
|
|
@ -8,12 +8,13 @@ import (
|
|||
"cwtch.im/cwtch/protocol/connections"
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/flutter/libcwtch-go/constants"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
)
|
||||
import "cwtch.im/cwtch/event"
|
||||
|
||||
type EventProfileEnvelope struct {
|
||||
event event.Event
|
||||
profile string
|
||||
Event event.Event
|
||||
Profile string
|
||||
}
|
||||
|
||||
type EventHandler struct {
|
||||
|
@ -81,39 +82,58 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
|
|||
}
|
||||
picPath := GetPicturePath(pic)
|
||||
|
||||
//tag, _ := profile.GetAttribute(app.AttributeTag)
|
||||
//tag, _ := Profile.GetAttribute(app.AttributeTag)
|
||||
|
||||
online, _ := profile.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
|
||||
|
||||
e.Data[constants.Name] = nick
|
||||
e.Data[constants.Picture] = picPath
|
||||
e.Data["Online"] = online
|
||||
|
||||
var contacts []Contact
|
||||
for _, contact := range profile.GetContacts() {
|
||||
cpicVal, ok := profile.GetContactAttribute(contact, attr.GetPeerScope(constants.Picture))
|
||||
if !ok {
|
||||
cpicVal = ImageToString(NewImage(RandomProfileImage(contact), TypeImageDistro))
|
||||
}
|
||||
cpic, err := StringToImage(cpicVal)
|
||||
if err != nil {
|
||||
cpic = NewImage(RandomProfileImage(contact), TypeImageDistro)
|
||||
}
|
||||
cpicPath := GetPicturePath(cpic)
|
||||
contactInfo := profile.GetContact(contact)
|
||||
contacts = append(contacts, Contact{Name: contactInfo.Name, Onion: contactInfo.Onion, Status: contactInfo.State, Picture: cpicPath,})
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(contacts)
|
||||
e.Data["ContactsJson"] = string(bytes)
|
||||
log.Infof("contactsJson %v", e.Data["ContactsJson"])
|
||||
}
|
||||
|
||||
json, _ := json.Marshal(e)
|
||||
return string(json)
|
||||
}
|
||||
|
||||
// handleProfileEvent enriches profile events so they are usable with out further data fetches
|
||||
// handleProfileEvent enriches Profile events so they are usable with out further data fetches
|
||||
func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
|
||||
|
||||
peer := eh.app.GetPeer(ev.profile)
|
||||
peer := eh.app.GetPeer(ev.Profile)
|
||||
ph := NewPeerHelper(peer)
|
||||
|
||||
switch ev.event.EventType {
|
||||
switch ev.Event.EventType {
|
||||
|
||||
/*case event.NetworkStatus:
|
||||
/*case Event.NetworkStatus:
|
||||
online, _ := peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
|
||||
if e.Data[event.Status] == plugins.NetworkCheckSuccess && online == event.False {
|
||||
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.True)
|
||||
if e.Data[Event.Status] == plugins.NetworkCheckSuccess && online == Event.False {
|
||||
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), Event.True)
|
||||
uiManager.UpdateNetworkStatus(true)
|
||||
// TODO we may have to reinitialize the peer
|
||||
} else if e.Data[event.Status] == plugins.NetworkCheckError && online == event.True {
|
||||
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False)
|
||||
} else if e.Data[Event.Status] == plugins.NetworkCheckError && online == Event.True {
|
||||
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), Event.False)
|
||||
uiManager.UpdateNetworkStatus(false)
|
||||
}*/
|
||||
|
||||
case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
|
||||
case event.NewMessageFromPeer: //Event.TimestampReceived, Event.RemotePeer, Event.Data
|
||||
// I Don't think we need to enrich
|
||||
|
||||
// TODO: deprecate and move to dart
|
||||
|
@ -127,45 +147,45 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
|
|||
}*/
|
||||
|
||||
// legacy
|
||||
//ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampReceived])
|
||||
//ts, _ := time.Parse(time.RFC3339Nano, e.Data[Event.TimestampReceived])
|
||||
|
||||
case event.PeerAcknowledgement:
|
||||
// No enrichement required
|
||||
//Acknowledge(ev.event.Data[event.RemotePeer], ev.event.Data[event.EventID])
|
||||
//Acknowledge(ev.Event.Data[Event.RemotePeer], ev.Event.Data[Event.EventID])
|
||||
/*
|
||||
case event.NewMessageFromGroup: //event.TimestampReceived, event.TimestampSent, event.Data, event.GroupID, event.RemotePeer
|
||||
ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampSent])
|
||||
uiManager.AddMessage(e.Data[event.GroupID], e.Data[event.RemotePeer], e.Data[event.Data], e.Data[event.RemotePeer] == peer.GetOnion(), hex.EncodeToString([]byte(e.Data[event.Signature])), ts, true)
|
||||
case Event.NewMessageFromGroup: //Event.TimestampReceived, Event.TimestampSent, Event.Data, Event.GroupID, Event.RemotePeer
|
||||
ts, _ := time.Parse(time.RFC3339Nano, e.Data[Event.TimestampSent])
|
||||
uiManager.AddMessage(e.Data[Event.GroupID], e.Data[Event.RemotePeer], e.Data[Event.Data], e.Data[Event.RemotePeer] == peer.GetOnion(), hex.EncodeToString([]byte(e.Data[Event.Signature])), ts, true)
|
||||
|
||||
case event.NewGroupInvite:
|
||||
gid, err := peer.ProcessInvite(e.Data[event.GroupInvite], e.Data[event.RemotePeer])
|
||||
case Event.NewGroupInvite:
|
||||
gid, err := peer.ProcessInvite(e.Data[Event.GroupInvite], e.Data[Event.RemotePeer])
|
||||
group := peer.GetGroup(gid)
|
||||
if err == nil && group != nil {
|
||||
uiManager.AddContact(gid)
|
||||
}
|
||||
*/
|
||||
case event.PeerCreated:
|
||||
handle := ev.event.Data[event.RemotePeer]
|
||||
handle := ev.Event.Data[event.RemotePeer]
|
||||
err := EnrichNewPeer(handle, ph, ev)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
/*
|
||||
case event.SendMessageToGroupError:
|
||||
uiManager.AddSendMessageError(e.Data[event.GroupServer], e.Data[event.Signature], e.Data[event.Error])
|
||||
case event.SendMessageToPeerError:
|
||||
uiManager.AddSendMessageError(e.Data[event.RemotePeer], e.Data[event.EventID], e.Data[event.Error])
|
||||
case Event.SendMessageToGroupError:
|
||||
uiManager.AddSendMessageError(e.Data[Event.GroupServer], e.Data[Event.Signature], e.Data[Event.Error])
|
||||
case Event.SendMessageToPeerError:
|
||||
uiManager.AddSendMessageError(e.Data[Event.RemotePeer], e.Data[Event.EventID], e.Data[Event.Error])
|
||||
*/
|
||||
case event.PeerStateChange:
|
||||
cxnState := connections.ConnectionStateToType[ev.event.Data[event.ConnectionState]]
|
||||
contact := peer.GetContact(ev.event.Data[event.RemotePeer])
|
||||
cxnState := connections.ConnectionStateToType[ev.Event.Data[event.ConnectionState]]
|
||||
contact := peer.GetContact(ev.Event.Data[event.RemotePeer])
|
||||
|
||||
if cxnState == connections.AUTHENTICATED && contact == nil {
|
||||
// Contact does not exist, change event to NewPeer
|
||||
peer.AddContact(ev.event.Data[event.RemotePeer], ev.event.Data[event.RemotePeer], model.AuthUnknown)
|
||||
contact = peer.GetContact(ev.event.Data[event.RemotePeer])
|
||||
ev.event.EventType = event.PeerCreated
|
||||
err := EnrichNewPeer(ev.event.Data[event.RemotePeer], ph, ev)
|
||||
// Contact does not exist, change Event to NewPeer
|
||||
peer.AddContact(ev.Event.Data[event.RemotePeer], ev.Event.Data[event.RemotePeer], model.AuthUnknown)
|
||||
contact = peer.GetContact(ev.Event.Data[event.RemotePeer])
|
||||
ev.Event.EventType = event.PeerCreated
|
||||
err := EnrichNewPeer(ev.Event.Data[event.RemotePeer], ph, ev)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
@ -176,17 +196,17 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
|
|||
//uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false)
|
||||
if cxnState == connections.AUTHENTICATED {
|
||||
// if known and authed, get vars
|
||||
peer.SendGetValToPeer(ev.event.Data[event.RemotePeer], attr.PublicScope, constants.Name)
|
||||
peer.SendGetValToPeer(ev.event.Data[event.RemotePeer], attr.PublicScope, constants.Picture)
|
||||
peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Name)
|
||||
peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Picture)
|
||||
}
|
||||
}
|
||||
|
||||
/*case event.NewRetValMessageFromPeer:
|
||||
onion := e.Data[event.RemotePeer]
|
||||
scope := e.Data[event.Scope]
|
||||
path := e.Data[event.Path]
|
||||
val := e.Data[event.Data]
|
||||
exists, _ := strconv.ParseBool(e.Data[event.Exists])
|
||||
/*case Event.NewRetValMessageFromPeer:
|
||||
onion := e.Data[Event.RemotePeer]
|
||||
scope := e.Data[Event.Scope]
|
||||
path := e.Data[Event.Path]
|
||||
val := e.Data[Event.Data]
|
||||
exists, _ := strconv.ParseBool(e.Data[Event.Exists])
|
||||
|
||||
if exists && scope == attr.PublicScope {
|
||||
switch path {
|
||||
|
@ -199,14 +219,14 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
|
|||
}
|
||||
}
|
||||
|
||||
case event.ServerStateChange:
|
||||
serverOnion := e.Data[event.GroupServer]
|
||||
state := connections.ConnectionStateToType[e.Data[event.ConnectionState]]
|
||||
case Event.ServerStateChange:
|
||||
serverOnion := e.Data[Event.GroupServer]
|
||||
state := connections.ConnectionStateToType[e.Data[Event.ConnectionState]]
|
||||
groups := peer.GetGroups()
|
||||
for _, groupID := range groups {
|
||||
group := peer.GetGroup(groupID)
|
||||
if group != nil && group.GroupServer == serverOnion {
|
||||
group.State = e.Data[event.ConnectionState]
|
||||
group.State = e.Data[Event.ConnectionState]
|
||||
loading := false
|
||||
if state == connections.AUTHENTICATED {
|
||||
loading = true
|
||||
|
@ -215,7 +235,7 @@ func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
|
|||
uiManager.UpdateContactStatus(serverOnion, int(state), loading)
|
||||
}
|
||||
}
|
||||
case event.DeletePeer:
|
||||
case Event.DeletePeer:
|
||||
log.Infof("PeerHandler got DeletePeer, SHUTTING down!\n")
|
||||
uiManager.ReloadProfiles()
|
||||
return
|
||||
|
@ -250,10 +270,10 @@ func (eh *EventHandler) startHandlingPeer(onion string) {
|
|||
}
|
||||
|
||||
func (eh *EventHandler) forwardProfileMessages(onion string, q event.Queue) {
|
||||
// TODO: graceful shutdown, via an injected event of special QUIT type exiting loop/go routine
|
||||
// TODO: graceful shutdown, via an injected Event of special QUIT type exiting loop/go routine
|
||||
for {
|
||||
e := q.Next()
|
||||
ev := EventProfileEnvelope{event: *e, profile: onion}
|
||||
ev := EventProfileEnvelope{Event: *e, Profile: onion}
|
||||
eh.profileEvents <- ev
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ func GetPicturePath(pic *image) string {
|
|||
case TypeImageDistro:
|
||||
return profilePicRelativize(pic.Val)
|
||||
default:
|
||||
log.Errorf("Unhandled profile picture type of %v\n", pic.T)
|
||||
log.Errorf("Unhandled Profile picture type of %v\n", pic.T)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ func getLastMessageTime(tl *model.Timeline) int {
|
|||
}
|
||||
|
||||
/*
|
||||
// AddProfile adds a new profile to the UI
|
||||
// AddProfile adds a new Profile to the UI
|
||||
func AddProfile(gcd *GrandCentralDispatcher, handle string) {
|
||||
p := the.CwtchApp.GetPeer(handle)
|
||||
if p != nil {
|
||||
|
@ -198,20 +198,20 @@ func AddProfile(gcd *GrandCentralDispatcher, handle string) {
|
|||
online, _ := p.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
|
||||
|
||||
log.Debugf("AddProfile %v %v %v %v %v\n", handle, nick, picPath, tag, online)
|
||||
gcd.AddProfile(handle, nick, picPath, tag, online == event.True)
|
||||
gcd.AddProfile(handle, nick, picPath, tag, online == Event.True)
|
||||
}
|
||||
}*/
|
||||
/*
|
||||
type manager struct {
|
||||
gcd *GrandCentralDispatcher
|
||||
profile string
|
||||
Profile string
|
||||
}
|
||||
|
||||
// Manager is a middleware helper for entities like peer event listeners wishing to trigger ui changes (via the gcd)
|
||||
// each manager is for one profile/peer
|
||||
// Manager is a middleware helper for entities like peer Event listeners wishing to trigger ui changes (via the gcd)
|
||||
// each manager is for one Profile/peer
|
||||
// manager takes minimal arguments and builds the full struct of data (usually pulled from a cwtch peer) required to call the GCD to perform the ui action
|
||||
// manager also performs call filtering based on UI state: users of manager can safely always call it on events and not have to worry about weather the relevant ui is active
|
||||
// ie: you can always safely call AddMessage even if in the ui a different profile is selected. manager will check with gcd, and if the correct conditions are not met, it will not call on gcd to update the ui incorrectly
|
||||
// ie: you can always safely call AddMessage even if in the ui a different Profile is selected. manager will check with gcd, and if the correct conditions are not met, it will not call on gcd to update the ui incorrectly
|
||||
type Manager interface {
|
||||
Acknowledge(handle, mID string)
|
||||
AddContact(Handle string)
|
||||
|
@ -234,9 +234,9 @@ type Manager interface {
|
|||
UpdateNetworkStatus(online bool)
|
||||
}
|
||||
|
||||
// NewManager returns a new Manager interface for a profile to the gcd
|
||||
func NewManager(profile string, gcd *GrandCentralDispatcher) Manager {
|
||||
return &manager{gcd: gcd, profile: profile}
|
||||
// NewManager returns a new Manager interface for a Profile to the gcd
|
||||
func NewManager(Profile string, gcd *GrandCentralDispatcher) Manager {
|
||||
return &manager{gcd: gcd, Profile: Profile}
|
||||
}
|
||||
|
||||
|
||||
|
@ -249,28 +249,28 @@ func EnrichNewPeer(handle string, ph *PeerHelper, ev *EventProfileEnvelope) erro
|
|||
group := ph.peer.GetGroup(handle)
|
||||
if group != nil {
|
||||
lastRead := ph.InitLastReadTime(group.GroupID)
|
||||
ev.event.Data["unread"] = strconv.Itoa(ph.CountUnread(group.Timeline.GetMessages(), lastRead))
|
||||
ev.event.Data["picture"] = ph.GetProfilePic(handle)
|
||||
ev.Event.Data["unread"] = strconv.Itoa(ph.CountUnread(group.Timeline.GetMessages(), lastRead))
|
||||
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
|
||||
|
||||
ev.event.Data["nick"] = ph.GetNick(handle)
|
||||
ev.event.Data["status"] = strconv.Itoa(int(connections.ConnectionStateToType[group.State]))
|
||||
ev.event.Data["authorization"] = string(model.AuthApproved)
|
||||
ev.event.Data["loading"] = "false"
|
||||
ev.event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&group.Timeline))
|
||||
ev.Event.Data["nick"] = ph.GetNick(handle)
|
||||
ev.Event.Data["status"] = strconv.Itoa(int(connections.ConnectionStateToType[group.State]))
|
||||
ev.Event.Data["authorization"] = string(model.AuthApproved)
|
||||
ev.Event.Data["loading"] = "false"
|
||||
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&group.Timeline))
|
||||
}
|
||||
} else if ph.IsPeer(handle) {
|
||||
contact := ph.peer.GetContact(handle)
|
||||
if contact != nil {
|
||||
lastRead := ph.InitLastReadTime(contact.Onion)
|
||||
ev.event.Data["unread"] = strconv.Itoa(ph.CountUnread(contact.Timeline.GetMessages(), lastRead))
|
||||
ev.event.Data["picture"] = ph.GetProfilePic(handle)
|
||||
ev.Event.Data["unread"] = strconv.Itoa(ph.CountUnread(contact.Timeline.GetMessages(), lastRead))
|
||||
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
|
||||
|
||||
ev.event.Data["nick"] = ph.GetNick(handle)
|
||||
ev.event.Data["status"] = strconv.Itoa(int(connections.ConnectionStateToType[contact.State]))
|
||||
ev.Event.Data["nick"] = ph.GetNick(handle)
|
||||
ev.Event.Data["status"] = strconv.Itoa(int(connections.ConnectionStateToType[contact.State]))
|
||||
|
||||
ev.event.Data["authorization"] = string(contact.Authorization)
|
||||
ev.event.Data["loading"] = "false"
|
||||
ev.event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&contact.Timeline))
|
||||
ev.Event.Data["authorization"] = string(contact.Authorization)
|
||||
ev.Event.Data["loading"] = "false"
|
||||
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&contact.Timeline))
|
||||
}
|
||||
} else {
|
||||
// could be a server?
|
||||
|
@ -283,7 +283,7 @@ func EnrichNewPeer(handle string, ph *PeerHelper, ev *EventProfileEnvelope) erro
|
|||
/*
|
||||
// AddSendMessageError adds an error not and icon to a message in a conversation in the ui for the message identified by the peer/sig combo
|
||||
func (this *manager) AddSendMessageError(peer string, signature string, err string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.DoIfProfile(this.Profile, func() {
|
||||
this.gcd.DoIfConversation(peer, func() {
|
||||
log.Debugf("Received Error Sending Message: %v", err)
|
||||
// FIXME: Sometimes, for the first Peer message we send our error beats our message to the UI
|
||||
|
@ -304,7 +304,7 @@ func (this *manager) MessageJustAdded() {
|
|||
/*
|
||||
// AddMessage adds a message to the message pane for the supplied conversation if it is active
|
||||
func (this *manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.DoIfProfile(this.Profile, func() {
|
||||
this.gcd.DoIfConversation(handle, func() {
|
||||
updateLastReadTime(handle)
|
||||
// If the message is not from the user then add it, otherwise, just acknowledge.
|
||||
|
@ -328,21 +328,21 @@ func (this *manager) ReloadProfiles() {
|
|||
|
||||
// UpdateContactDisplayName updates a contact's display name in the contact list and conversations
|
||||
func (this *manager) UpdateContactDisplayName(handle string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.DoIfProfile(this.Profile, func() {
|
||||
this.gcd.UpdateContactDisplayName(handle, GetNick(handle))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateContactPicture updates a contact's picture in the contact list and conversations
|
||||
func (this *manager) UpdateContactPicture(handle string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.DoIfProfile(this.Profile, func() {
|
||||
this.gcd.UpdateContactPicture(handle, GetProfilePic(handle))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateContactAttribute update's a contacts attribute in the ui
|
||||
func (this *manager) UpdateContactAttribute(handle, key, value string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.DoIfProfile(this.Profile, func() {
|
||||
this.gcd.UpdateContactAttribute(handle, key, value)
|
||||
})
|
||||
}
|
||||
|
@ -352,6 +352,6 @@ func (this *manager) ChangePasswordResponse(error bool) {
|
|||
}
|
||||
|
||||
func (this *manager) UpdateNetworkStatus(online bool) {
|
||||
this.gcd.UpdateProfileNetworkStatus(this.profile, online)
|
||||
this.gcd.UpdateProfileNetworkStatus(this.Profile, online)
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue