You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

149 lines
3.7 KiB

  1. // Package giphy implements a Service which adds !commands for Giphy.
  2. package giphy
  3. import (
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "strings"
  10. "github.com/matrix-org/go-neb/types"
  11. log "github.com/sirupsen/logrus"
  12. "maunium.net/go/mautrix/event"
  13. mevt "maunium.net/go/mautrix/event"
  14. "maunium.net/go/mautrix/id"
  15. )
  16. // ServiceType of the Giphy service.
  17. const ServiceType = "giphy"
  18. type image struct {
  19. URL string `json:"url"`
  20. // Giphy returns ints as strings..
  21. Width string `json:"width"`
  22. Height string `json:"height"`
  23. Size string `json:"size"`
  24. }
  25. type result struct {
  26. Slug string `json:"slug"`
  27. Images struct {
  28. Downsized image `json:"downsized"`
  29. Original image `json:"original"`
  30. } `json:"images"`
  31. }
  32. type giphySearch struct {
  33. Data result `json:"data"`
  34. }
  35. // Service contains the Config fields for the Giphy Service.
  36. //
  37. // Example request:
  38. // {
  39. // "api_key": "dc6zaTOxFJmzC",
  40. // "use_downsized": false
  41. // }
  42. type Service struct {
  43. types.DefaultService
  44. // The Giphy API key to use when making HTTP requests to Giphy.
  45. // The public beta API key is "dc6zaTOxFJmzC".
  46. APIKey string `json:"api_key"`
  47. // Whether to use the downsized image from Giphy.
  48. // Uses the original image when set to false.
  49. // Defaults to false.
  50. UseDownsized bool `json:"use_downsized"`
  51. }
  52. // Commands supported:
  53. // !giphy some search query without quotes
  54. // Responds with a suitable GIF into the same room as the command.
  55. func (s *Service) Commands(client types.MatrixClient) []types.Command {
  56. return []types.Command{
  57. types.Command{
  58. Path: []string{"giphy"},
  59. Command: func(roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
  60. return s.cmdGiphy(client, roomID, userID, args)
  61. },
  62. },
  63. }
  64. }
  65. func (s *Service) cmdGiphy(client types.MatrixClient, roomID id.RoomID, userID id.UserID, args []string) (interface{}, error) {
  66. // only 1 arg which is the text to search for.
  67. query := strings.Join(args, " ")
  68. gifResult, err := s.searchGiphy(query)
  69. if err != nil {
  70. return nil, err
  71. }
  72. image := gifResult.Images.Original
  73. if s.UseDownsized {
  74. image = gifResult.Images.Downsized
  75. }
  76. if image.URL == "" {
  77. return nil, fmt.Errorf("No results")
  78. }
  79. resUpload, err := client.UploadLink(image.URL)
  80. if err != nil {
  81. return nil, err
  82. }
  83. return mevt.MessageEventContent{
  84. MsgType: event.MsgImage,
  85. Body: gifResult.Slug,
  86. URL: resUpload.ContentURI.CUString(),
  87. Info: &mevt.FileInfo{
  88. Height: asInt(image.Height),
  89. Width: asInt(image.Width),
  90. MimeType: "image/gif",
  91. Size: asInt(image.Size),
  92. },
  93. }, nil
  94. }
  95. // searchGiphy returns info about a gif
  96. func (s *Service) searchGiphy(query string) (*result, error) {
  97. log.Info("Searching giphy for ", query)
  98. u, err := url.Parse("http://api.giphy.com/v1/gifs/translate")
  99. if err != nil {
  100. return nil, err
  101. }
  102. q := u.Query()
  103. q.Set("s", query)
  104. q.Set("api_key", s.APIKey)
  105. u.RawQuery = q.Encode()
  106. res, err := http.Get(u.String())
  107. if res != nil {
  108. defer res.Body.Close()
  109. }
  110. if err != nil {
  111. return nil, err
  112. }
  113. var search giphySearch
  114. if err := json.NewDecoder(res.Body).Decode(&search); err != nil {
  115. // Giphy returns a JSON object which has { data: [] } if there are 0 results.
  116. // This fails to be deserialised by Go.
  117. return nil, fmt.Errorf("No results")
  118. }
  119. return &search.Data, nil
  120. }
  121. func asInt(strInt string) int {
  122. i64, err := strconv.ParseInt(strInt, 10, 32)
  123. if err != nil {
  124. return 0 // default to 0 since these are all just hints to the client
  125. }
  126. return int(i64)
  127. }
  128. func init() {
  129. types.RegisterService(func(serviceID string, serviceUserID id.UserID, webhookEndpointURL string) types.Service {
  130. return &Service{
  131. DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
  132. }
  133. })
  134. }