Load member lists and avatar (low res) from facebook
This commit is contained in:
parent
7431469632
commit
1316a3031b
8 changed files with 190 additions and 81 deletions
16
account.go
16
account.go
|
@ -293,12 +293,14 @@ func (a *Account) userInfoUpdatedInternal(user UserID, info *UserInfo) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if MediaObject(info.Avatar) != nil {
|
if info.Avatar.MediaObject != nil {
|
||||||
cache_key := fmt.Sprintf("%s/user_avatar/%s", a.Protocol, user)
|
cache_key := fmt.Sprintf("%s/user_avatar/%s", a.Protocol, user)
|
||||||
cache_val := info.Avatar.Filename()
|
cache_val := info.Avatar.Filename()
|
||||||
if cache_val == "" || dbKvTestAndSet(cache_key, cache_val) {
|
if cache_val == "" || dbKvGet(cache_key) != cache_val {
|
||||||
err2 := mx.ProfileAvatar(mx_user_id, info.Avatar)
|
err2 := mx.ProfileAvatar(mx_user_id, info.Avatar)
|
||||||
if err2 != nil {
|
if err2 == nil {
|
||||||
|
dbKvPut(cache_key, cache_val)
|
||||||
|
} else {
|
||||||
err = err2
|
err = err2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,12 +349,14 @@ func (a *Account) roomInfoUpdatedInternal(roomId RoomID, author UserID, info *Ro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if MediaObject(info.Picture) != nil {
|
if info.Picture.MediaObject != nil {
|
||||||
cache_key := fmt.Sprintf("%s/room_picture/%s", a.Protocol, roomId)
|
cache_key := fmt.Sprintf("%s/room_picture/%s", a.Protocol, roomId)
|
||||||
cache_val := info.Picture.Filename()
|
cache_val := info.Picture.Filename()
|
||||||
if cache_val == "" || dbKvTestAndSet(cache_key, cache_val) {
|
if cache_val == "" || dbKvGet(cache_key) != cache_val {
|
||||||
err2 := mx.RoomAvatarAs(mx_room_id, info.Picture, as_mxid)
|
err2 := mx.RoomAvatarAs(mx_room_id, info.Picture, as_mxid)
|
||||||
if err2 != nil {
|
if err2 == nil {
|
||||||
|
dbKvPut(cache_key, cache_val)
|
||||||
|
} else {
|
||||||
err = err2
|
err = err2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
48
connector/external/external.go
vendored
48
connector/external/external.go
vendored
|
@ -1,6 +1,7 @@
|
||||||
package external
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -179,7 +180,7 @@ func (ext *External) restartLoop(generation int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *extMessageWithData) DecodeJSON(jj []byte) error {
|
func (m *extMessageWithData) UnmarshalJSON(jj []byte) error {
|
||||||
var c extMessage
|
var c extMessage
|
||||||
|
|
||||||
err := json.Unmarshal(jj, &c)
|
err := json.Unmarshal(jj, &c)
|
||||||
|
@ -189,40 +190,59 @@ func (m *extMessageWithData) DecodeJSON(jj []byte) error {
|
||||||
*m = extMessageWithData{extMessage: c}
|
*m = extMessageWithData{extMessage: c}
|
||||||
switch c.MsgType {
|
switch c.MsgType {
|
||||||
case USER_INFO_UPDATED:
|
case USER_INFO_UPDATED:
|
||||||
var ui UserInfo
|
var ui struct {
|
||||||
|
Data UserInfo `json:"data"`
|
||||||
|
}
|
||||||
err := json.Unmarshal(jj, &ui)
|
err := json.Unmarshal(jj, &ui)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.Data = &ui
|
m.Data = &ui.Data
|
||||||
|
return nil
|
||||||
case ROOM_INFO_UPDATED:
|
case ROOM_INFO_UPDATED:
|
||||||
var ri RoomInfo
|
var ri struct {
|
||||||
|
Data RoomInfo `json:"data"`
|
||||||
|
}
|
||||||
err := json.Unmarshal(jj, &ri)
|
err := json.Unmarshal(jj, &ri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.Data = &ri
|
m.Data = &ri.Data
|
||||||
|
return nil
|
||||||
case EVENT:
|
case EVENT:
|
||||||
var ev Event
|
var ev struct {
|
||||||
|
Data Event `json:"data"`
|
||||||
|
}
|
||||||
err := json.Unmarshal(jj, &ev)
|
err := json.Unmarshal(jj, &ev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.Data = &ev
|
m.Data = &ev.Data
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
|
case JOINED, LEFT, CACHE_PUT, CACHE_GET, REP_OK, REP_ERROR:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Invalid message type for message from external program: '%s'", c.MsgType)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ext *External) recvLoop() {
|
func (ext *External) recvLoop() {
|
||||||
reader := json.NewDecoder(ext.recv)
|
scanner := bufio.NewScanner(ext.recv)
|
||||||
for {
|
for scanner.Scan() {
|
||||||
var msg extMessageWithData
|
var msg extMessageWithData
|
||||||
err := reader.Decode(&msg)
|
err := json.Unmarshal(scanner.Bytes(), &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to decode from %s: %s. Stopping reading.", ext.command, err)
|
log.Warnf("Failed to decode from %s: %s. Skipping line.", ext.command, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
log.Warnf("Failed to read from %s: %s. Stopping here.", ext.command, scanner.Err().Error())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("GOT MESSAGE: %#v %#v", msg, msg.Data)
|
||||||
if strings.HasPrefix(msg.MsgType, "rep_") {
|
if strings.HasPrefix(msg.MsgType, "rep_") {
|
||||||
func() {
|
func() {
|
||||||
ext.lock.Lock()
|
ext.lock.Lock()
|
||||||
|
@ -233,7 +253,7 @@ func (ext *External) recvLoop() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
ext.handleCmd(&msg)
|
go ext.handleCmd(&msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,7 +348,7 @@ func (ext *External) User() UserID {
|
||||||
MsgType: GET_USER,
|
MsgType: GET_USER,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Unable to get user!")
|
log.Warnf("Unable to get user! %s", err.Error())
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return rep.User
|
return rep.User
|
||||||
|
|
|
@ -142,7 +142,7 @@ func (irc *IRC) SetRoomInfo(roomId RoomID, info *RoomInfo) error {
|
||||||
if info.Name != "" && info.Name != ch {
|
if info.Name != "" && info.Name != ch {
|
||||||
return fmt.Errorf("May not change IRC room name to other than %s", ch)
|
return fmt.Errorf("May not change IRC room name to other than %s", ch)
|
||||||
}
|
}
|
||||||
if MediaObject(info.Picture) != nil {
|
if info.Picture.MediaObject != nil {
|
||||||
return fmt.Errorf("Room picture not supported on IRC")
|
return fmt.Errorf("Room picture not supported on IRC")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,18 +6,28 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
S_EVENT_JOIN = "join"
|
||||||
|
S_EVENT_LEAVE = "leave"
|
||||||
|
S_EVENT_MESSAGE = "message"
|
||||||
|
S_EVENT_ACTION = "action"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) MarshalText() ([]byte, error) {
|
func (t EventType) MarshalText() ([]byte, error) {
|
||||||
switch t {
|
switch t {
|
||||||
case EVENT_JOIN:
|
case EVENT_JOIN:
|
||||||
return []byte("join"), nil
|
return []byte(S_EVENT_JOIN), nil
|
||||||
case EVENT_LEAVE:
|
case EVENT_LEAVE:
|
||||||
return []byte("leave"), nil
|
return []byte(S_EVENT_LEAVE), nil
|
||||||
case EVENT_MESSAGE:
|
case EVENT_MESSAGE:
|
||||||
return []byte("message"), nil
|
return []byte(S_EVENT_MESSAGE), nil
|
||||||
case EVENT_ACTION:
|
case EVENT_ACTION:
|
||||||
return []byte("action"), nil
|
return []byte(S_EVENT_ACTION), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Invalid event type: %d", t)
|
return nil, fmt.Errorf("Invalid event type: %d", t)
|
||||||
}
|
}
|
||||||
|
@ -25,16 +35,16 @@ func (t EventType) MarshalText() ([]byte, error) {
|
||||||
|
|
||||||
func (t *EventType) UnmarshalText(text []byte) error {
|
func (t *EventType) UnmarshalText(text []byte) error {
|
||||||
switch string(text) {
|
switch string(text) {
|
||||||
case "join":
|
case S_EVENT_JOIN:
|
||||||
*t = EVENT_JOIN
|
*t = EVENT_JOIN
|
||||||
return nil
|
return nil
|
||||||
case "leave":
|
case S_EVENT_LEAVE:
|
||||||
*t = EVENT_LEAVE
|
*t = EVENT_LEAVE
|
||||||
return nil
|
return nil
|
||||||
case "message":
|
case S_EVENT_MESSAGE:
|
||||||
*t = EVENT_MESSAGE
|
*t = EVENT_MESSAGE
|
||||||
return nil
|
return nil
|
||||||
case "action":
|
case S_EVENT_ACTION:
|
||||||
*t = EVENT_ACTION
|
*t = EVENT_ACTION
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
@ -47,20 +57,27 @@ func (t *EventType) UnmarshalText(text []byte) error {
|
||||||
type MediaObjectJSON struct {
|
type MediaObjectJSON struct {
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
Mimetype string `json:"mime_type"`
|
Mimetype string `json:"mime_type"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
ImageSize *ImageSize `json:"image_size"`
|
ImageSize *ImageSize `json:"image_size"`
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mo SMediaObject) MarshalJSON() ([]byte, error) {
|
func (mo SMediaObject) MarshalJSON() ([]byte, error) {
|
||||||
if MediaObject(mo) == nil {
|
if mo.MediaObject == nil {
|
||||||
return []byte("null"), nil
|
return []byte("null"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := MediaObjectJSON{
|
mod := MediaObjectJSON{
|
||||||
Filename: mo.Filename(),
|
Filename: mo.Filename(),
|
||||||
Mimetype: mo.Mimetype(),
|
Mimetype: mo.Mimetype(),
|
||||||
|
Size: mo.Size(),
|
||||||
ImageSize: mo.ImageSize(),
|
ImageSize: mo.ImageSize(),
|
||||||
|
URL: mo.URL(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mod.URL == "" {
|
||||||
|
// If we don't have a URL, the only way is to pass the blob itself
|
||||||
rd, err := mo.Read()
|
rd, err := mo.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -72,6 +89,8 @@ func (mo SMediaObject) MarshalJSON() ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mod.Data = base64.StdEncoding.EncodeToString(buf.Bytes())
|
mod.Data = base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
return json.Marshal(&mod)
|
return json.Marshal(&mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +104,27 @@ func (mo *SMediaObject) UnmarshalJSON(jdata []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.URL != "" {
|
||||||
|
*mo = SMediaObject{&LazyBlobMediaObject{
|
||||||
|
ObjectFilename: d.Filename,
|
||||||
|
ObjectMimetype: d.Mimetype,
|
||||||
|
ObjectImageSize: d.ImageSize,
|
||||||
|
GetFn: func(o *LazyBlobMediaObject) error {
|
||||||
|
resp, err := http.Get(d.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if o.ObjectMimetype == "" {
|
||||||
|
o.ObjectMimetype = strings.Join(resp.Header["Content-Type"], "")
|
||||||
|
}
|
||||||
|
o.ObjectData, err = ioutil.ReadAll(resp.Body)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
bytes, err := base64.StdEncoding.DecodeString(d.Data)
|
bytes, err := base64.StdEncoding.DecodeString(d.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -210,7 +210,7 @@ func (mm *Mattermost) SetRoomInfo(roomId RoomID, info *RoomInfo) error {
|
||||||
mm.conn.UpdateChannelHeader(ch, info.Topic)
|
mm.conn.UpdateChannelHeader(ch, info.Topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
if MediaObject(info.Picture) != nil {
|
if info.Picture.MediaObject != nil {
|
||||||
err = fmt.Errorf("Not supported: channel picture on mattermost")
|
err = fmt.Errorf("Not supported: channel picture on mattermost")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,44 +57,6 @@ func (m *FileMediaObject) URL() string {
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
type UrlMediaObject struct {
|
|
||||||
ObjectFilename string
|
|
||||||
ObjectSize int64
|
|
||||||
ObjectMimetype string
|
|
||||||
ObjectURL string
|
|
||||||
ObjectImageSize *ImageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *UrlMediaObject) Filename() string {
|
|
||||||
return m.ObjectFilename
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *UrlMediaObject) Size() int64 {
|
|
||||||
return m.ObjectSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *UrlMediaObject) Mimetype() string {
|
|
||||||
return m.ObjectMimetype
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *UrlMediaObject) ImageSize() *ImageSize {
|
|
||||||
return m.ObjectImageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *UrlMediaObject) Read() (io.ReadCloser, error) {
|
|
||||||
resp, err := http.Get(m.ObjectURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.Body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *UrlMediaObject) URL() string {
|
|
||||||
return m.ObjectURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----
|
|
||||||
|
|
||||||
type BlobMediaObject struct {
|
type BlobMediaObject struct {
|
||||||
ObjectFilename string
|
ObjectFilename string
|
||||||
ObjectMimetype string
|
ObjectMimetype string
|
||||||
|
|
|
@ -272,7 +272,7 @@ func (xm *XMPP) SetRoomInfo(roomId RoomID, info *RoomInfo) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if MediaObject(info.Picture) != nil {
|
if info.Picture.MediaObject != nil {
|
||||||
// TODO
|
// TODO
|
||||||
return fmt.Errorf("Room picture change not implemented on xmpp")
|
return fmt.Errorf("Room picture change not implemented on xmpp")
|
||||||
}
|
}
|
||||||
|
|
85
external/messenger.py
vendored
85
external/messenger.py
vendored
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import gevent
|
|
||||||
import fbchat
|
import fbchat
|
||||||
|
from fbchat.models import *
|
||||||
|
|
||||||
# ---- MESSAGE TYPES ----
|
# ---- MESSAGE TYPES ----
|
||||||
|
|
||||||
|
@ -35,21 +38,96 @@ CACHE_GET = "cache_get"
|
||||||
REP_OK = "rep_ok"
|
REP_OK = "rep_ok"
|
||||||
REP_ERROR = "rep_error"
|
REP_ERROR = "rep_error"
|
||||||
|
|
||||||
|
# Event types
|
||||||
|
EVENT_JOIN = "join"
|
||||||
|
EVENT_LEAVE = "leave"
|
||||||
|
EVENT_MESSAGE = "message"
|
||||||
|
EVENT_ACTION = "action"
|
||||||
|
|
||||||
|
|
||||||
# ---- MESSENGER CLIENT CLASS THAT HANDLES EVENTS ----
|
# ---- MESSENGER CLIENT CLASS THAT HANDLES EVENTS ----
|
||||||
|
|
||||||
|
def getUserId(user):
|
||||||
|
if user.url is not None and not "?" in user.url:
|
||||||
|
return user.url.split("/")[-1]
|
||||||
|
else:
|
||||||
|
return user.uid
|
||||||
|
|
||||||
|
def mediaObjectOfURL(url):
|
||||||
|
return {
|
||||||
|
"filename": url.split("?")[0].split("/")[-1],
|
||||||
|
"url": url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MessengerBridgeClient(fbchat.Client):
|
class MessengerBridgeClient(fbchat.Client):
|
||||||
def __init__(self, bridge, *args, **kwargs):
|
def __init__(self, bridge, *args, **kwargs):
|
||||||
self.bridge = bridge
|
self.bridge = bridge
|
||||||
|
|
||||||
super(MessengerBridgeClient, self).__init__(*args, **kwargs)
|
super(MessengerBridgeClient, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
class InitialSyncThread(threading.Thread):
|
||||||
|
def __init__(self, client, bridge, *args, **kwargs):
|
||||||
|
super(InitialSyncThread, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.client = client
|
||||||
|
self.bridge = bridge
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
threads = self.client.fetchThreadList()
|
||||||
|
sys.stderr.write("fb thread list: {}\n".format(threads))
|
||||||
|
for thread in threads:
|
||||||
|
sys.stderr.write("fb thread: {}\n".format(thread))
|
||||||
|
if thread.type != ThreadType.GROUP:
|
||||||
|
continue
|
||||||
|
self.bridge.write({
|
||||||
|
"_type": JOINED,
|
||||||
|
"room": thread.uid,
|
||||||
|
})
|
||||||
|
|
||||||
|
room_info = {
|
||||||
|
"name": thread.name,
|
||||||
|
}
|
||||||
|
if thread.photo is not None:
|
||||||
|
room_info["picture"] = mediaObjectOfURL(thread.photo)
|
||||||
|
self.bridge.write({
|
||||||
|
"_type": ROOM_INFO_UPDATED,
|
||||||
|
"room": thread.uid,
|
||||||
|
"data": room_info,
|
||||||
|
})
|
||||||
|
|
||||||
|
members = self.client.fetchAllUsersFromThreads([thread])
|
||||||
|
for member in members:
|
||||||
|
sys.stderr.write("fb thread member: {}\n".format(member))
|
||||||
|
self.bridge.write({
|
||||||
|
"_type": EVENT,
|
||||||
|
"data": {
|
||||||
|
"type": EVENT_JOIN,
|
||||||
|
"author": getUserId(member),
|
||||||
|
"room": thread.uid,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
user_info = {
|
||||||
|
"display_name": member.name,
|
||||||
|
}
|
||||||
|
if member.photo is not None:
|
||||||
|
user_info["avatar"] = mediaObjectOfURL(member.photo)
|
||||||
|
self.bridge.write({
|
||||||
|
"_type": USER_INFO_UPDATED,
|
||||||
|
"user": getUserId(member),
|
||||||
|
"data": user_info,
|
||||||
|
})
|
||||||
|
|
||||||
# TODO: handle events
|
# TODO: handle events
|
||||||
|
|
||||||
|
|
||||||
# ---- MAIN LOOP THAT HANDLES REQUESTS FROM BRIDGE ----
|
# ---- MAIN LOOP THAT HANDLES REQUESTS FROM BRIDGE ----
|
||||||
|
|
||||||
class MessengerBridge:
|
class MessengerBridge:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.client = None
|
self.client = None
|
||||||
self.keep_running = True
|
self.keep_running = True
|
||||||
|
@ -104,10 +182,15 @@ class MessengerBridge:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
InitialSyncThread(self.client, self).start()
|
||||||
|
|
||||||
elif ty == CLOSE:
|
elif ty == CLOSE:
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
self.keep_running = False
|
self.keep_running = False
|
||||||
|
|
||||||
|
elif ty == GET_USER:
|
||||||
|
return {"_type": REP_OK, "user": self.client.uid}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return {"_type": REP_ERROR, "error": "Not implemented"}
|
return {"_type": REP_ERROR, "error": "Not implemented"}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue