bullet.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package bullet
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "mime"
  7. "net/http"
  8. "net/url"
  9. "path/filepath"
  10. "time"
  11. )
  12. const (
  13. shortTimeFormat = "2006-01-02 15:04"
  14. )
  15. //Bullet client for the Pushbullet API
  16. type Bullet struct {
  17. token string
  18. baseURL string
  19. }
  20. //NewBullet creates new Bullet using provided token
  21. func NewBullet(token string) Bullet {
  22. b := Bullet{token: token, baseURL: "https://api.pushbullet.com/v2"}
  23. return b
  24. }
  25. func (b Bullet) newRequest(body io.Reader, URL string) (*http.Request, error) {
  26. request, err := http.NewRequest(http.MethodPost, URL, body)
  27. if err != nil {
  28. return nil, err
  29. }
  30. request.Header.Add("Access-Token", b.token)
  31. request.Header.Add("Content-Type", "application/json")
  32. return request, nil
  33. }
  34. func (b Bullet) newRequestPush(body io.Reader) (*http.Request, error) {
  35. return b.newRequest(body, b.baseURL+"/pushes")
  36. }
  37. func (b Bullet) newRequestUpload(body io.Reader) (*http.Request, error) {
  38. return b.newRequest(body, b.baseURL+"/upload-request")
  39. }
  40. func doRequest(request *http.Request) (*http.Response, error) {
  41. client := http.Client{}
  42. response, errResponse := client.Do(request)
  43. if errResponse != nil {
  44. return nil, errResponse
  45. }
  46. if response.StatusCode != http.StatusOK {
  47. defer response.Body.Close()
  48. var errBullet bulletError
  49. decoder := json.NewDecoder(response.Body)
  50. errJSON := decoder.Decode(&errBullet)
  51. if errJSON != nil {
  52. return nil, errJSON
  53. }
  54. return nil, errBullet.getError()
  55. }
  56. return response, nil
  57. }
  58. func (b Bullet) send(push pushInterface) error {
  59. reader, errReader := push.getReader()
  60. if errReader != nil {
  61. return errReader
  62. }
  63. request, errRequest := b.newRequestPush(reader)
  64. if errRequest != nil {
  65. return errRequest
  66. }
  67. response, errResponse := doRequest(request)
  68. if errResponse != nil {
  69. return errResponse
  70. }
  71. response.Body.Close()
  72. return nil
  73. }
  74. //SendNote sends push note with given title and text, use empty string as deviceID to send to all
  75. func (b Bullet) SendNote(title, text, deviceID string) error {
  76. note := newNotePush(title, text, deviceID)
  77. err := b.send(note)
  78. return err
  79. }
  80. //SendLink sends push link with given title, text and link, use empty string as deviceID to send to all
  81. func (b Bullet) SendLink(title, text, link, deviceID string) error {
  82. linkPush := newLinkPush(title, text, link, deviceID)
  83. err := b.send(linkPush)
  84. return err
  85. }
  86. func (b Bullet) requestUpload(file fileUpload) (*fileUpload, error) {
  87. reader, errReader := file.getReader()
  88. if errReader != nil {
  89. return nil, errReader
  90. }
  91. request, errRequest := b.newRequestUpload(reader)
  92. if errRequest != nil {
  93. return nil, errRequest
  94. }
  95. response, errResponse := doRequest(request)
  96. if errResponse != nil {
  97. return nil, errResponse
  98. }
  99. defer response.Body.Close()
  100. var result fileUpload
  101. decoder := json.NewDecoder(response.Body)
  102. errJSON := decoder.Decode(&result)
  103. if errJSON != nil {
  104. return nil, errJSON
  105. }
  106. return &result, nil
  107. }
  108. func (b Bullet) uploadFile(path string) (*fileUpload, error) {
  109. extension := filepath.Ext(path)
  110. mimeType := mime.TypeByExtension(extension)
  111. if mimeType == "" {
  112. mimeType = "application/octet-stream"
  113. }
  114. filename := filepath.Base(path)
  115. fileUpload := newFileUpload(filename, mimeType)
  116. uploadResponse, errUpload := b.requestUpload(fileUpload)
  117. if errUpload != nil {
  118. return nil, errUpload
  119. }
  120. request, errRequest := getFileRequest(path, *uploadResponse)
  121. if errRequest != nil {
  122. return nil, errRequest
  123. }
  124. client := http.Client{}
  125. response, errResponse := client.Do(request)
  126. if errResponse != nil {
  127. return nil, errResponse
  128. }
  129. defer response.Body.Close()
  130. if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
  131. return nil, fmt.Errorf("Error uploading file: %s", response.Status)
  132. }
  133. return uploadResponse, nil
  134. }
  135. //SendFile sends file push with given title, text and file, use empty string as deviceID to send to all
  136. func (b Bullet) SendFile(title, text, file, deviceID string) error {
  137. uploadResult, errUpload := b.uploadFile(file)
  138. if errUpload != nil {
  139. return errUpload
  140. }
  141. if title != "" {
  142. uploadResult.Title = title
  143. } else {
  144. uploadResult.Title = time.Now().Format(shortTimeFormat)
  145. }
  146. uploadResult.Body = text
  147. uploadResult.Type = "file"
  148. uploadResult.DeviceID = deviceID
  149. err := b.send(uploadResult)
  150. return err
  151. }
  152. // ListDevices returns Devices structure which contains slice of devices
  153. func (b Bullet) ListDevices() (*Devices, error) {
  154. request, err := http.NewRequest(http.MethodGet, b.baseURL+"/devices", nil)
  155. if err != nil {
  156. return nil, err
  157. }
  158. request.Header.Add("Access-Token", b.token)
  159. response, errResponse := doRequest(request)
  160. if errResponse != nil {
  161. return nil, errResponse
  162. }
  163. defer response.Body.Close()
  164. var result Devices
  165. decoder := json.NewDecoder(response.Body)
  166. errJSON := decoder.Decode(&result)
  167. if errJSON != nil {
  168. return nil, errJSON
  169. }
  170. return &result, nil
  171. }
  172. // ListPushes returns Pushes structure which contains slice of pushes
  173. func (b Bullet) ListPushes(active bool, modifiedAfter *time.Time, limit int, cursor string) (*Pushes, error) {
  174. params := url.Values{}
  175. if active {
  176. params.Add("active", "true")
  177. } else {
  178. params.Add("active", "false")
  179. }
  180. if modifiedAfter != nil {
  181. params.Add("modified_after", fmt.Sprint(modifiedAfter.Unix()))
  182. }
  183. if limit > 0 {
  184. params.Add("limit", fmt.Sprint(limit))
  185. }
  186. params.Add("cursor", cursor)
  187. URL := fmt.Sprintf("%s/pushes?%s", b.baseURL, params.Encode())
  188. request, err := http.NewRequest(http.MethodGet, URL, nil)
  189. if err != nil {
  190. return nil, err
  191. }
  192. request.Header.Add("Access-Token", b.token)
  193. response, errResponse := doRequest(request)
  194. if errResponse != nil {
  195. return nil, errResponse
  196. }
  197. defer response.Body.Close()
  198. var result Pushes
  199. decoder := json.NewDecoder(response.Body)
  200. errJSON := decoder.Decode(&result)
  201. if errJSON != nil {
  202. return nil, errJSON
  203. }
  204. return &result, nil
  205. }