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.

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