diff --git a/connector/connector.go b/connector/connector.go index 48fc9e7..c2edda4 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -59,6 +59,9 @@ type Connector interface { // Leave a channel Leave(roomId RoomID) + // Search for users + SearchForUsers(query string) ([]UserSearchResult, error) + // Send an event. Returns the ID of the created remote message. // This ID is used to deduplicate messages: if it comes back, it should have the same Id // than the one returned here. @@ -153,6 +156,11 @@ type RoomInfo struct { Picture SMediaObject `json:"picture"` } +type UserSearchResult struct { + ID UserID `json:"id"` + DisplayName string `json:"display_name"` +} + type MediaObject interface { Filename() string Size() int64 diff --git a/connector/external/external.go b/connector/external/external.go index 637e69e..c40d55b 100644 --- a/connector/external/external.go +++ b/connector/external/external.go @@ -49,6 +49,7 @@ const ( JOIN = "join" INVITE = "invite" LEAVE = "leave" + SEARCH = "search" SEND = "send" CLOSE = "close" @@ -64,8 +65,9 @@ const ( // reply messages // ezbr -> external: all must wait for a reply! // external -> ezbr: only CACHE_GET produces a reply - REP_OK = "rep_ok" - REP_ERROR = "rep_error" + REP_OK = "rep_ok" + REP_SEARCH_RESULTS = "rep_search_results" + REP_ERROR = "rep_error" ) // ---- @@ -226,6 +228,16 @@ func (m *extMessageWithData) UnmarshalJSON(jj []byte) error { } m.Data = &ev.Data return nil + case REP_SEARCH_RESULTS: + var ev struct { + Data []UserSearchResult `json:"data"` + } + err := json.Unmarshal(jj, &ev) + if err != nil { + return err + } + m.Data = ev.Data + return nil case JOINED, LEFT, CACHE_PUT, CACHE_GET, REP_OK, REP_ERROR: return nil default: @@ -428,6 +440,19 @@ func (ext *External) Leave(room RoomID) { } } +func (ext *External) SearchForUsers(query string) ([]UserSearchResult, error) { + rep, err := ext.cmd(extMessage{ + MsgType: SEARCH, + }, query) + if err != nil { + return nil, err + } + if rep.MsgType != REP_SEARCH_RESULTS { + return nil, fmt.Errorf("Invalid result type from external: %s", rep.MsgType) + } + return rep.Data.([]UserSearchResult), nil +} + func (ext *External) Send(event *Event) (string, error) { rep, err := ext.cmd(extMessage{ MsgType: SEND, diff --git a/connector/irc/irc.go b/connector/irc/irc.go index bf36dfe..cf93893 100644 --- a/connector/irc/irc.go +++ b/connector/irc/irc.go @@ -198,6 +198,11 @@ func (irc *IRC) Leave(roomId RoomID) { irc.conn.Cmd.Part(ch) } +func (irc *IRC) SearchForUsers(query string) ([]UserSearchResult, error) { + // TODO + return nil, fmt.Errorf("Not implemented") +} + func (irc *IRC) Send(event *Event) (string, error) { if irc.conn == nil { return "", fmt.Errorf("Not connected") diff --git a/connector/mattermost/mattermost.go b/connector/mattermost/mattermost.go index 9410126..4490f2a 100644 --- a/connector/mattermost/mattermost.go +++ b/connector/mattermost/mattermost.go @@ -253,6 +253,11 @@ func (mm *Mattermost) Leave(roomId RoomID) { // Not supported? TODO } +func (mm *Mattermost) SearchForUsers(query string) ([]UserSearchResult, error) { + // TODO + return nil, fmt.Errorf("Not implemented") +} + func (mm *Mattermost) Send(event *Event) (string, error) { post := &model.Post{ Message: event.Text, diff --git a/connector/xmpp/xmpp.go b/connector/xmpp/xmpp.go index 1f77fdf..483c75e 100644 --- a/connector/xmpp/xmpp.go +++ b/connector/xmpp/xmpp.go @@ -306,6 +306,11 @@ func (xm *XMPP) Leave(roomId RoomID) { xm.conn.LeaveMUC(string(roomId)) } +func (xm *XMPP) SearchForUsers(query string) ([]UserSearchResult, error) { + // TODO: search roster + return nil, fmt.Errorf("Not implemented") +} + func (xm *XMPP) Send(event *Event) (string, error) { if event.Attachments != nil && len(event.Attachments) > 0 { for _, at := range event.Attachments { diff --git a/external/messenger.py b/external/messenger.py index 9874b5b..6da4808 100755 --- a/external/messenger.py +++ b/external/messenger.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 -# @FIXME: make revUserId work correctly (e.g.: talking to a contact not in your top 20 recent threads will fail) - could we do this with a search request? # @FIXME: store the client pickle in the config automatically -# @FIXME: add a way to search for users and initiate a discussion import sys import json @@ -31,6 +29,7 @@ SET_ROOM_INFO = "set_room_info" JOIN = "join" INVITE = "invite" LEAVE = "leave" +SEARCH = "search" SEND = "send" CLOSE = "close" @@ -47,6 +46,7 @@ CACHE_GET = "cache_get" # ezbr -> external: all must wait for a reply! # external -> ezbr: only CACHE_GET produces a reply REP_OK = "rep_ok" +REP_SEARCH_RESULTS = "rep_search_results" REP_ERROR = "rep_error" # Event types @@ -168,13 +168,14 @@ class MessengerBridge: return self.getUserId(user) def revUserId(self, user_id): - if user_id in self.rev_uid: - return self.rev_uid[user_id] - else: - # TODO this doesn't work always... - # we should try to find the api endpoint that resolves usernames ? - # or do a search request and try to find a user that has that username - return user_id + if user_id not in self.rev_uid: + for user in self.client.searchForUsers(user_id): + self.getUserId(user) + + if user_id not in self.rev_uid: + raise ValueError("User not found: {}".format(user_id)) + + return self.rev_uid[user_id] def getUserShortName(self, user): if user.first_name != None: @@ -277,6 +278,16 @@ class MessengerBridge: uid = self.revUserId(cmd["user"]) self.client.addUsersToGroup([uid], cmd["room"]) + elif ty == SEARCH: + users = self.client.searchForUsers(cmd["data"]) + rep = [] + for user in users: + rep.append({ + "id": self.getUserId(user), + "display_name": user.name, + }) + return {"_type": REP_SEARCH_RESULTS, "data": rep} + elif ty == SEND: event = cmd["data"] if event["type"] in [EVENT_MESSAGE, EVENT_ACTION]: diff --git a/server.go b/server.go index 294ad7f..84b1893 100644 --- a/server.go +++ b/server.go @@ -228,6 +228,7 @@ func handleSystemMessage(mxid string, msg string) { ezbrSystemSend(mxid, "- accounts: list accounts") ezbrSystemSend(mxid, "- join : join public chat room") ezbrSystemSend(mxid, "- talk : open private conversation to contact") + ezbrSystemSend(mxid, "- search : search for users by name") case "list", "account", "accounts": one := false if accts, ok := registeredAccounts[mxid]; ok { @@ -271,6 +272,21 @@ func handleSystemMessage(mxid string, msg string) { } else { ezbrSystemSendf(mxid, "No account with name or using protocol %s", cmd[1]) } + case "search": + account := findAccount(mxid, cmd[1]) + if account != nil { + rep, err := account.Conn.SearchForUsers(cmd[2]) + if err != nil { + ezbrSystemSendf(mxid, "Search error: %s", err) + } else { + ezbrSystemSendf(mxid, "%d users found", len(rep)) + for i, user := range rep { + ezbrSystemSendf(mxid, "- %s (%s)", i+1, user.DisplayName, user.ID) + } + } + } else { + ezbrSystemSendf(mxid, "No account with name or using protocol %s", cmd[1]) + } default: ezbrSystemSend(mxid, "Unrecognized command. Type `help` if you need some help!") }