Skip to content

Commit

Permalink
custom emoji aliases (keybase#23303)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaxim authored Mar 27, 2020
1 parent e541834 commit f849641
Show file tree
Hide file tree
Showing 24 changed files with 628 additions and 47 deletions.
104 changes: 97 additions & 7 deletions go/chat/emojisource.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"regexp"
"strings"
"sync"
"time"

"github.com/keybase/client/go/chat/attachments"
"github.com/keybase/client/go/chat/globals"
Expand Down Expand Up @@ -90,6 +91,53 @@ func (s *DevConvEmojiSource) Add(ctx context.Context, uid gregor1.UID, convID ch
return s.addAdvanced(ctx, uid, convID, alias, filename, nil, storage)
}

func (s *DevConvEmojiSource) AddAlias(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
newAlias, existingAlias string) (res chat1.EmojiRemoteSource, err error) {
defer s.Trace(ctx, func() error { return err }, "AddAlias")()
if strings.Contains(newAlias, "#") {
return res, errors.New("invalid character in emoji alias")
}
var stored chat1.EmojiStorage
storage := s.makeStorage(chat1.TopicType_EMOJI)
topicName := s.topicName(nil)
if _, _, err := storage.Get(ctx, uid, convID, topicName, &stored, false); err != nil {
return res, err
}
getExistingMsgSrc := func() (res chat1.EmojiRemoteSource, found bool) {
if stored.Mapping == nil {
return res, false
}
existingSource, ok := stored.Mapping[existingAlias]
if !ok {
return res, false
}
if !existingSource.IsMessage() {
return res, false
}
return existingSource, true
}
msgSrc, ok := getExistingMsgSrc()
if ok {
res = chat1.NewEmojiRemoteSourceWithMessage(chat1.EmojiMessage{
ConvID: msgSrc.Message().ConvID,
MsgID: msgSrc.Message().MsgID,
IsAlias: true,
})
} else {
username, err := s.G().GetUPAKLoader().LookupUsername(ctx, keybase1.UID(uid.String()))
if err != nil {
return res, err
}
res = chat1.NewEmojiRemoteSourceWithStockalias(chat1.EmojiStockAlias{
Text: existingAlias,
Username: username.String(),
Time: gregor1.ToTime(time.Now()),
})
}
stored.Mapping[newAlias] = res
return res, storage.Put(ctx, uid, convID, topicName, stored)
}

func (s *DevConvEmojiSource) removeRemoteSource(ctx context.Context, uid gregor1.UID,
conv chat1.ConversationLocal, source chat1.EmojiRemoteSource) error {
typ, err := source.Typ()
Expand All @@ -98,11 +146,18 @@ func (s *DevConvEmojiSource) removeRemoteSource(ctx context.Context, uid gregor1
}
switch typ {
case chat1.EmojiRemoteSourceTyp_MESSAGE:
if source.Message().IsAlias {
s.Debug(ctx, "removeRemoteSource: skipping asset remove on alias")
return nil
}
return s.G().ChatHelper.DeleteMsg(ctx, source.Message().ConvID, conv.Info.TlfName,
source.Message().MsgID)
case chat1.EmojiRemoteSourceTyp_STOCKALIAS:
// do nothing
default:
return fmt.Errorf("unable to delete remote source of typ: %v", typ)
}
return nil
}

func (s *DevConvEmojiSource) Remove(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
Expand Down Expand Up @@ -134,6 +189,15 @@ func (s *DevConvEmojiSource) Remove(ctx context.Context, uid gregor1.UID, convID
return err
}
delete(stored.Mapping, alias)
// take out any aliases
if source.IsMessage() {
for existingAlias, existingSource := range stored.Mapping {
if existingSource.IsMessage() && existingSource.Message().IsAlias &&
existingSource.Message().MsgID == source.Message().MsgID {
delete(stored.Mapping, existingAlias)
}
}
}
return storage.Put(ctx, uid, convID, topicName, stored)
}

Expand All @@ -147,6 +211,8 @@ func (s *DevConvEmojiSource) remoteToLocalSource(ctx context.Context, remote cha
msg := remote.Message()
url := s.G().AttachmentURLSrv.GetURL(ctx, msg.ConvID, msg.MsgID, false)
return chat1.NewEmojiLoadSourceWithHttpsrv(url), nil
case chat1.EmojiRemoteSourceTyp_STOCKALIAS:
return chat1.NewEmojiLoadSourceWithStr(remote.Stockalias().Text), nil
default:
return res, errors.New("unknown remote source for local source")
}
Expand All @@ -173,13 +239,18 @@ func (s *DevConvEmojiSource) creationInfo(ctx context.Context, uid gregor1.UID,
Username: sourceMsg.Valid().SenderUsername,
Time: sourceMsg.Valid().ServerHeader.Ctime,
}, nil
case chat1.EmojiRemoteSourceTyp_STOCKALIAS:
return chat1.EmojiCreationInfo{
Username: remote.Stockalias().Username,
Time: remote.Stockalias().Time,
}, nil
default:
return res, errors.New("unknown remote source for creation info")
}
}

func (s *DevConvEmojiSource) getNoSet(ctx context.Context, uid gregor1.UID, convID *chat1.ConversationID,
getCreationInfo bool) (res chat1.UserEmojis, aliasLookup map[string]chat1.Emoji, err error) {
opts chat1.EmojiFetchOpts) (res chat1.UserEmojis, aliasLookup map[string]chat1.Emoji, err error) {
aliasLookup = make(map[string]chat1.Emoji)
topicType := chat1.TopicType_EMOJI
storage := s.makeStorage(topicType)
Expand All @@ -205,6 +276,9 @@ func (s *DevConvEmojiSource) getNoSet(ctx context.Context, uid gregor1.UID, conv
}
convs := ibox.Convs
addEmojis := func(convs []chat1.ConversationLocal, isCrossTeam bool) {
if opts.OnlyInTeam {
return
}
for _, conv := range convs {
var stored chat1.EmojiStorage
found, err := storage.GetFromKnownConv(ctx, uid, conv, &stored)
Expand All @@ -220,13 +294,16 @@ func (s *DevConvEmojiSource) getNoSet(ctx context.Context, uid gregor1.UID, conv
Name: conv.Info.TlfName,
}
for alias, storedEmoji := range stored.Mapping {
if !opts.GetAliases && storedEmoji.IsAlias() {
continue
}
var creationInfo *chat1.EmojiCreationInfo
source, err := s.remoteToLocalSource(ctx, storedEmoji)
if err != nil {
s.Debug(ctx, "Get: skipping emoji on remote-to-local error: %s", err)
continue
}
if getCreationInfo {
if opts.GetCreationInfo {
ci, err := s.creationInfo(ctx, uid, storedEmoji)
if err != nil {
s.Debug(ctx, "Get: failed to get creation info: %s", err)
Expand Down Expand Up @@ -268,10 +345,10 @@ func (s *DevConvEmojiSource) getNoSet(ctx context.Context, uid gregor1.UID, conv
}

func (s *DevConvEmojiSource) Get(ctx context.Context, uid gregor1.UID, convID *chat1.ConversationID,
getCreationInfo bool) (res chat1.UserEmojis, err error) {
opts chat1.EmojiFetchOpts) (res chat1.UserEmojis, err error) {
defer s.Trace(ctx, func() error { return err }, "Get")()
var aliasLookup map[string]chat1.Emoji
if res, aliasLookup, err = s.getNoSet(ctx, uid, convID, getCreationInfo); err != nil {
if res, aliasLookup, err = s.getNoSet(ctx, uid, convID, opts); err != nil {
return res, err
}
s.getLock.Lock()
Expand Down Expand Up @@ -341,8 +418,17 @@ func (s *DevConvEmojiSource) versionMatch(ctx context.Context, uid gregor1.UID,

func (s *DevConvEmojiSource) syncCrossTeam(ctx context.Context, uid gregor1.UID, emoji chat1.HarvestedEmoji,
convID chat1.ConversationID) (res chat1.HarvestedEmoji, err error) {
if !emoji.Source.IsMessage() {
return res, errors.New("can only sync message remote source")
typ, err := emoji.Source.Typ()
if err != nil {
return res, err
}
switch typ {
case chat1.EmojiRemoteSourceTyp_MESSAGE:
case chat1.EmojiRemoteSourceTyp_STOCKALIAS:
emoji.IsCrossTeam = true
return emoji, nil
default:
return res, errors.New("invalid remote source to sync")
}
suffix := convID.String()
var stored chat1.EmojiStorage
Expand Down Expand Up @@ -409,7 +495,11 @@ func (s *DevConvEmojiSource) Harvest(ctx context.Context, body string, uid grego
ctx = globals.CtxMakeEmojiHarvester(ctx)
defer s.Trace(ctx, func() error { return err }, "Harvest: mode: %v", mode)()
s.Debug(ctx, "Harvest: %d matches found", len(matches))
emojis, _, err := s.getNoSet(ctx, uid, &convID, false)
emojis, _, err := s.getNoSet(ctx, uid, &convID, chat1.EmojiFetchOpts{
GetCreationInfo: false,
GetAliases: true,
OnlyInTeam: false,
})
if err != nil {
return res, err
}
Expand Down
67 changes: 65 additions & 2 deletions go/chat/emojisource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ func TestEmojiSourceBasic(t *testing.T) {
_, err = tc.Context().EmojiSource.Add(ctx, uid, teamConv.Id, "party_parrot2", filename)
require.NoError(t, err)

res, err := tc.Context().EmojiSource.Get(ctx, uid, nil, true)
res, err := tc.Context().EmojiSource.Get(ctx, uid, nil, chat1.EmojiFetchOpts{
GetCreationInfo: true,
GetAliases: true,
})
require.NoError(t, err)
require.Equal(t, 2, len(res.Emojis))
for _, group := range res.Emojis {
Expand Down Expand Up @@ -97,7 +100,10 @@ func TestEmojiSourceBasic(t *testing.T) {
require.True(t, source.IsMessage())
_, err = GetMessage(ctx, tc.Context(), uid, source.Message().ConvID, source.Message().MsgID, true, nil)
require.Error(t, err)
res, err = tc.Context().EmojiSource.Get(ctx, uid, &conv.Id, true)
res, err = tc.Context().EmojiSource.Get(ctx, uid, &conv.Id, chat1.EmojiFetchOpts{
GetCreationInfo: true,
GetAliases: true,
})
require.NoError(t, err)
checked := false
for _, group := range res.Emojis {
Expand All @@ -107,6 +113,63 @@ func TestEmojiSourceBasic(t *testing.T) {
}
}
require.True(t, checked)

t.Logf("alias")
_, err = tc.Context().EmojiSource.AddAlias(ctx, uid, conv.Id, "mike2", "mike")
require.NoError(t, err)
res, err = tc.Context().EmojiSource.Get(ctx, uid, &conv.Id, chat1.EmojiFetchOpts{
GetCreationInfo: true,
GetAliases: true,
})
require.NoError(t, err)
checked = false
for _, group := range res.Emojis {
if group.Name == conv.TlfName {
require.Equal(t, 2, len(group.Emojis))
checked = true
}
}
require.True(t, checked)
msgID = mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{
Body: "ITS TIME :mike2:!",
}))
checkEmoji(ctx, t, tc, uid, conv, msgID, "mike2")
require.NoError(t, tc.Context().EmojiSource.Remove(ctx, uid, conv.Id, "mike2"))
res, err = tc.Context().EmojiSource.Get(ctx, uid, &conv.Id, chat1.EmojiFetchOpts{
GetCreationInfo: true,
GetAliases: true,
})
require.NoError(t, err)
checked = false
for _, group := range res.Emojis {
if group.Name == conv.TlfName {
t.Logf("emojis: %+v", group.Emojis)
require.Equal(t, 1, len(group.Emojis))
checked = true
}
}
require.True(t, checked)
msgID = mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{
Body: "ITS TIME :mike:!",
}))
checkEmoji(ctx, t, tc, uid, conv, msgID, "mike")
_, err = tc.Context().EmojiSource.AddAlias(ctx, uid, conv.Id, "mike2", "mike")
require.NoError(t, err)
require.NoError(t, tc.Context().EmojiSource.Remove(ctx, uid, conv.Id, "mike"))
res, err = tc.Context().EmojiSource.Get(ctx, uid, &conv.Id, chat1.EmojiFetchOpts{
GetCreationInfo: true,
GetAliases: true,
})
require.NoError(t, err)
checked = false
for _, group := range res.Emojis {
if group.Name == conv.TlfName {
require.Zero(t, len(group.Emojis))
checked = true
}
}
require.True(t, checked)

}

func TestEmojiSourceCrossTeam(t *testing.T) {
Expand Down
16 changes: 15 additions & 1 deletion go/chat/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3702,6 +3702,20 @@ func (h *Server) AddEmoji(ctx context.Context, arg chat1.AddEmojiArg) (res chat1
return res, nil
}

func (h *Server) AddEmojiAlias(ctx context.Context, arg chat1.AddEmojiAliasArg) (res chat1.AddEmojiRes, err error) {
ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
defer h.Trace(ctx, func() error { return err }, "AddEmojiAlias")()
defer func() { h.setResultRateLimit(ctx, &res) }()
uid, err := utils.AssertLoggedInUID(ctx, h.G())
if err != nil {
return res, err
}
if _, err := h.G().EmojiSource.AddAlias(ctx, uid, arg.ConvID, arg.NewAlias, arg.ExistingAlias); err != nil {
return res, err
}
return res, nil
}

func (h *Server) RemoveEmoji(ctx context.Context, arg chat1.RemoveEmojiArg) (res chat1.RemoveEmojiRes, err error) {
ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, h.identNotifier)
defer h.Trace(ctx, func() error { return err }, "RemoveEmoji")()
Expand All @@ -3724,6 +3738,6 @@ func (h *Server) UserEmojis(ctx context.Context, arg chat1.UserEmojisArg) (res c
if err != nil {
return res, err
}
res.Emojis, err = h.G().EmojiSource.Get(ctx, uid, arg.ConvID, arg.GetCreationInfo)
res.Emojis, err = h.G().EmojiSource.Get(ctx, uid, arg.ConvID, arg.Opts)
return res, err
}
4 changes: 3 additions & 1 deletion go/chat/types/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,10 @@ type ParticipantSource interface {

type EmojiSource interface {
Add(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, alias, filename string) (chat1.EmojiRemoteSource, error)
AddAlias(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
newAlias, existingAlias string) (chat1.EmojiRemoteSource, error)
Remove(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, alias string) error
Get(ctx context.Context, uid gregor1.UID, convID *chat1.ConversationID, getCreationInfo bool) (chat1.UserEmojis, error)
Get(ctx context.Context, uid gregor1.UID, convID *chat1.ConversationID, opts chat1.EmojiFetchOpts) (chat1.UserEmojis, error)
Decorate(ctx context.Context, body string, convID chat1.ConversationID, emojis []chat1.HarvestedEmoji) string
Harvest(ctx context.Context, body string, uid gregor1.UID, convID chat1.ConversationID,
crossTeams map[string]chat1.HarvestedEmoji, mode EmojiSourceHarvestMode) ([]chat1.HarvestedEmoji, error)
Expand Down
6 changes: 5 additions & 1 deletion go/chat/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,12 +803,16 @@ func (DummyEmojiSource) Add(ctx context.Context, uid gregor1.UID, convID chat1.C
alias, filename string) (res chat1.EmojiRemoteSource, err error) {
return res, err
}
func (DummyEmojiSource) AddAlias(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
newAlias, existingAlias string) (res chat1.EmojiRemoteSource, err error) {
return res, err
}
func (DummyEmojiSource) Remove(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
alias string) error {
return nil
}
func (DummyEmojiSource) Get(ctx context.Context, uid gregor1.UID, convID *chat1.ConversationID,
getCreationInfo bool) (chat1.UserEmojis, error) {
opts chat1.EmojiFetchOpts) (chat1.UserEmojis, error) {
return chat1.UserEmojis{}, nil
}
func (DummyEmojiSource) Decorate(ctx context.Context, body string, convID chat1.ConversationID,
Expand Down
3 changes: 3 additions & 0 deletions go/client/chat_api_doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ List members of a conversation from a conversation id:
Add an emoji:
{"method": "emojiadd", "params": {"options": {"channel": {"name": "mikem"}, "alias": "mask-parrot2", "filename": "/Users/mike/Downloads/mask-parrot.gif"}}}
Add an emoji alias:
{"method": "emojiaddalias", "params": {"options": {"channel": {"name": "mikem"}, "new_alias": "mask-parrot2", "existing_alias": "mask-parrot"}}}
Remove an emoji:
{"method": "emojiremove", "params": {"options": {"channel": {"name": "mikem"}, "alias": "mask-parrot2"}}}
Expand Down
Loading

0 comments on commit f849641

Please sign in to comment.