bullet.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. func (b Bullet) get(URL string) (io.ReadCloser, error) {
  153. request, err := http.NewRequest(http.MethodGet, URL, nil)
  154. if err != nil {
  155. return nil, err
  156. }
  157. request.Header.Add("Access-Token", b.token)
  158. response, errResponse := doRequest(request)
  159. if errResponse != nil {
  160. return nil, errResponse
  161. }
  162. return response.Body, nil
  163. }
  164. // ListDevices returns Devices structure which contains slice of devices
  165. func (b Bullet) ListDevices() (*Devices, error) {
  166. body, err := b.get(b.baseURL + "/devices")
  167. if err != nil {
  168. return nil, err
  169. }
  170. defer body.Close()
  171. var result Devices
  172. decoder := json.NewDecoder(body)
  173. errJSON := decoder.Decode(&result)
  174. if errJSON != nil {
  175. return nil, errJSON
  176. }
  177. return &result, nil
  178. }
  179. // ListPushes returns Pushes structure which contains slice of pushes, limit <= 0 gives default of 500
  180. func (b Bullet) ListPushes(active bool, modifiedAfter *time.Time, limit int, cursor string) (*Pushes, error) {
  181. params := url.Values{}
  182. if active {
  183. params.Add("active", "true")
  184. } else {
  185. params.Add("active", "false")
  186. }
  187. if modifiedAfter != nil {
  188. params.Add("modified_after", fmt.Sprint(modifiedAfter.Unix()))
  189. }
  190. if limit > 0 {
  191. params.Add("limit", fmt.Sprint(limit))
  192. }
  193. params.Add("cursor", cursor)
  194. URL := fmt.Sprintf("%s/pushes?%s", b.baseURL, params.Encode())
  195. body, err := b.get(URL)
  196. if err != nil {
  197. return nil, err
  198. }
  199. defer body.Close()
  200. var result Pushes
  201. decoder := json.NewDecoder(body)
  202. errJSON := decoder.Decode(&result)
  203. if errJSON != nil {
  204. return nil, errJSON
  205. }
  206. return &result, nil
  207. }