Load member lists and avatar (low res) from facebook

This commit is contained in:
Alex 2020-02-29 20:47:44 +01:00
parent 7431469632
commit 1316a3031b
8 changed files with 190 additions and 81 deletions

View file

@ -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
} }
} }

View file

@ -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
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)
} }
return nil
} }
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

View file

@ -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

View file

@ -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,31 +57,40 @@ 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(),
} }
rd, err := mo.Read()
if err != nil { if mod.URL == "" {
return nil, err // If we don't have a URL, the only way is to pass the blob itself
rd, err := mo.Read()
if err != nil {
return nil, err
}
defer rd.Close()
buf := bytes.NewBuffer([]byte{})
_, err = io.Copy(buf, rd)
if err != nil {
return nil, err
}
mod.Data = base64.StdEncoding.EncodeToString(buf.Bytes())
} }
defer rd.Close()
buf := bytes.NewBuffer([]byte{})
_, err = io.Copy(buf, rd)
if err != nil {
return nil, err
}
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

View file

@ -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")
} }

View file

@ -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

View file

@ -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
View file

@ -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"}