github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/commands/giphy.go (about) 1 package commands 2 3 import ( 4 "context" 5 "sync" 6 7 "github.com/keybase/client/go/chat/giphy" 8 "github.com/keybase/client/go/chat/globals" 9 "github.com/keybase/client/go/chat/types" 10 "github.com/keybase/client/go/libkb" 11 "github.com/keybase/client/go/protocol/chat1" 12 "github.com/keybase/client/go/protocol/gregor1" 13 ) 14 15 type giphySearcher interface { 16 Search(mctx libkb.MetaContext, apiKeySource types.ExternalAPIKeySource, query *string, limit int, 17 urlsrv types.AttachmentURLSrv) ([]chat1.GiphySearchResult, error) 18 } 19 20 type defaultGiphySearcher struct { 21 globals.Contextified 22 } 23 24 func (d defaultGiphySearcher) Search(mctx libkb.MetaContext, apiKeySource types.ExternalAPIKeySource, 25 query *string, limit int, urlsrv types.AttachmentURLSrv) ([]chat1.GiphySearchResult, error) { 26 return giphy.Search(d.G(), mctx, apiKeySource, query, limit, urlsrv) 27 } 28 29 type Giphy struct { 30 sync.Mutex 31 *baseCommand 32 shownResults map[chat1.ConvIDStr]*string 33 shownWindow map[chat1.ConvIDStr]bool 34 currentOpCancelFn context.CancelFunc 35 currentOpDoneCb chan struct{} 36 searcher giphySearcher 37 } 38 39 func NewGiphy(g *globals.Context) *Giphy { 40 usage := "Search for and post GIFs" 41 return &Giphy{ 42 baseCommand: newBaseCommand(g, "giphy", "[search terms]", usage, true), 43 shownResults: make(map[chat1.ConvIDStr]*string), 44 shownWindow: make(map[chat1.ConvIDStr]bool), 45 searcher: defaultGiphySearcher{ 46 Contextified: globals.NewContextified(g), 47 }, 48 } 49 } 50 51 func (s *Giphy) getQuery(text string) *string { 52 var query *string 53 _, q, err := s.commandAndMessage(text) 54 if err != nil { 55 return nil 56 } 57 if len(q) > 0 { 58 query = &q 59 } 60 return query 61 } 62 63 func (s *Giphy) getLimit() int { 64 limit := 25 65 if s.G().IsMobileAppType() { 66 limit = 10 67 } 68 return limit 69 } 70 71 func (s *Giphy) Execute(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 72 tlfName, text string, replyTo *chat1.MessageID) (err error) { 73 if !s.Match(ctx, text) { 74 return ErrInvalidCommand 75 } 76 results, err := s.searcher.Search(libkb.NewMetaContext(ctx, s.G().ExternalG()), 77 s.G().ExternalAPIKeySource, s.getQuery(text), s.getLimit(), s.G().AttachmentURLSrv) 78 if err != nil { 79 s.Debug(ctx, "Execute: failed to get Giphy results: %s", err) 80 return err 81 } 82 if len(results) == 0 { 83 s.Debug(ctx, "Execute: failed to find any results") 84 return nil 85 } 86 res := results[libkb.RandIntn(len(results))] 87 _, err = s.G().ChatHelper.SendTextByIDNonblock(ctx, convID, tlfName, res.TargetUrl, nil, replyTo) 88 return err 89 } 90 91 func (s *Giphy) queryEqual(query *string, shown *string) bool { 92 if query == nil && shown == nil { 93 return true 94 } else if query == nil && shown != nil || query != nil && shown == nil { 95 return false 96 } 97 return *query == *shown 98 } 99 100 func (s *Giphy) Preview(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 101 tlfName, text string) { 102 defer s.Trace(ctx, nil, "Preview")() 103 s.Lock() 104 if s.currentOpCancelFn != nil { 105 s.currentOpCancelFn() 106 } 107 select { 108 case <-s.currentOpDoneCb: 109 s.Debug(ctx, "Preview: waiting for previous run to terminate") 110 default: 111 } 112 s.Debug(ctx, "Preview: cleared for takeoff") 113 ctx, s.currentOpCancelFn = context.WithCancel(ctx) 114 s.currentOpDoneCb = make(chan struct{}) 115 s.Unlock() 116 defer close(s.currentOpDoneCb) 117 118 if !s.Match(ctx, text) { 119 if _, ok := s.shownWindow[convID.ConvIDStr()]; ok { 120 // tell UI to clear 121 err := s.getChatUI().ChatGiphyToggleResultWindow(ctx, convID, false, false) 122 if err != nil { 123 s.Debug(ctx, "Preview: error on toggle result: %+v", err) 124 } 125 delete(s.shownResults, convID.ConvIDStr()) 126 delete(s.shownWindow, convID.ConvIDStr()) 127 } 128 return 129 } 130 query := s.getQuery(text) 131 if shown, ok := s.shownResults[convID.ConvIDStr()]; ok && s.queryEqual(query, shown) { 132 s.Debug(ctx, "Preview: same query given, skipping") 133 return 134 } 135 err := s.getChatUI().ChatGiphyToggleResultWindow(ctx, convID, true, false) 136 if err != nil { 137 s.Debug(ctx, "Preview: error on toggle result: %+v", err) 138 } 139 140 s.shownWindow[convID.ConvIDStr()] = true 141 142 results, err := s.searcher.Search(libkb.NewMetaContext(ctx, s.G().ExternalG()), 143 s.G().ExternalAPIKeySource, query, s.getLimit(), s.G().AttachmentURLSrv) 144 if err != nil { 145 s.Debug(ctx, "Preview: failed to get Giphy results: %s", err) 146 return 147 } 148 err = s.getChatUI().ChatGiphySearchResults(ctx, convID, chat1.GiphySearchResults{ 149 Results: results, 150 GalleryUrl: s.G().AttachmentURLSrv.GetGiphyGalleryURL(ctx, convID, tlfName, results), 151 }) 152 if err != nil { 153 s.Debug(ctx, "Preview: error on search results: %+v", err) 154 return 155 } 156 157 s.shownResults[convID.ConvIDStr()] = query 158 }