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.

147 lines
3.5 KiB

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