/lib/tumblr.js

https://gitlab.com/kevintcoughlin/tumblr.js · JavaScript · 253 lines · 187 code · 56 blank · 10 comment · 32 complexity · 0ff12c22ee63e5ab01423f185be9a4a7 MD5 · raw file

  1. var fs = require('fs');
  2. var qs = require('querystring');
  3. function TumblrClient(credentials) {
  4. this.credentials = credentials || {};
  5. }
  6. var request;
  7. module.exports = {
  8. Client: TumblrClient,
  9. createClient: function (credentials) {
  10. return new TumblrClient(credentials);
  11. },
  12. request: function(r) {
  13. request = r;
  14. }
  15. };
  16. var baseURL = 'http://api.tumblr.com/v2';
  17. var calls = {
  18. postCreation: function (type, requireOptions, acceptsData) {
  19. return function (blogName, options, callback) {
  20. requireValidation(options, requireOptions);
  21. options.type = type;
  22. if (!acceptsData) {
  23. delete options.data;
  24. }
  25. this._post(blogPath(blogName, '/post'), options, callback);
  26. };
  27. },
  28. getWithOptions: function (path) {
  29. return function (options, callback) {
  30. if (isFunction(options)) { callback = options; options = {}; }
  31. this._get(path, options, callback);
  32. };
  33. },
  34. blogList: function (path) {
  35. return function (blogName, options, callback) {
  36. if (isFunction(options)) { callback = options; options = {}; }
  37. this._get(blogPath(blogName, path), options, callback);
  38. };
  39. }
  40. };
  41. TumblrClient.prototype = {
  42. // Tagged
  43. tagged: function (tag, options, callback) {
  44. if (isFunction(options)) { callback = options; options = {}; }
  45. options = options || {};
  46. options.tag = tag;
  47. this._get('/tagged', options, callback, true);
  48. },
  49. // Blogs
  50. blogInfo: function (blogName, callback) {
  51. this._get(blogPath(blogName, '/info'), {}, callback, true);
  52. },
  53. avatar: function (blogName, size, callback) {
  54. if (isFunction(size)) { callback = size; size = null; }
  55. var path = size ? '/avatar/' + size : '/avatar';
  56. this._get(blogPath(blogName, path), {}, callback, true);
  57. },
  58. blogLikes: function (blogName, options, callback) {
  59. if (isFunction(options)) { callback = options; options = {}; }
  60. this._get(blogPath(blogName, '/likes'), options, callback, true);
  61. },
  62. followers: function (blogName, options, callback) {
  63. if (isFunction(options)) { callback = options; options = {}; }
  64. this._get(blogPath(blogName, '/followers'), options, callback);
  65. },
  66. posts: function (blogName, options, callback) {
  67. if (isFunction(options)) { callback = options; options = {}; }
  68. options = options || {};
  69. var path = options.type ? '/posts/' + options.type : '/posts';
  70. this._get(blogPath(blogName, path), options, callback, true);
  71. },
  72. queue: calls.blogList('/posts/queue'),
  73. drafts: calls.blogList('/posts/draft'),
  74. submissions: calls.blogList('/posts/submission'),
  75. // Posts
  76. edit: function (blogName, options, callback) {
  77. this._post(blogPath(blogName, '/post/edit'), options, callback);
  78. },
  79. reblog: function (blogName, options, callback) {
  80. this._post(blogPath(blogName, '/post/reblog'), options, callback);
  81. },
  82. deletePost: function (blogName, id, callback) {
  83. this._post(blogPath(blogName, '/post/delete'), {id: id}, callback);
  84. },
  85. photo: calls.postCreation('photo', ['data', 'source'], true),
  86. audio: calls.postCreation('audio', ['data', 'external_url'], true),
  87. video: calls.postCreation('video', ['data', 'embed'], true),
  88. quote: calls.postCreation('quote', ['quote'], false),
  89. text: calls.postCreation('text', ['body'], false),
  90. link: calls.postCreation('link', ['url'], false),
  91. chat: calls.postCreation('chat', ['conversation'], false),
  92. // User
  93. userInfo: function (callback) {
  94. this._get('/user/info', {}, callback);
  95. },
  96. likes: function (options, callback) {
  97. if (isFunction(options)) { callback = options; options = {}; }
  98. this._get('/user/likes', options, callback);
  99. },
  100. follow: function (url, callback) {
  101. this._post('/user/follow', {url: url}, callback);
  102. },
  103. unfollow: function (url, callback) {
  104. this._post('/user/unfollow', {url: url}, callback);
  105. },
  106. like: function (id, reblogKey, callback) {
  107. this._post('/user/like', {id: id, reblog_key: reblogKey}, callback);
  108. },
  109. unlike: function (id, reblogKey, callback) {
  110. this._post('/user/unlike', {id: id, reblog_key: reblogKey}, callback);
  111. },
  112. dashboard: calls.getWithOptions('/user/dashboard'),
  113. following: calls.getWithOptions('/user/following'),
  114. // Helpers
  115. _get: function (path, params, callback, addApiKey) {
  116. params = params || {};
  117. if (addApiKey) {
  118. params.api_key = this.credentials.consumer_key;
  119. }
  120. request.get({
  121. url: baseURL + path + '?' + qs.stringify(params),
  122. json: true,
  123. oauth: this.credentials,
  124. followRedirect: false
  125. }, requestCallback(callback));
  126. },
  127. _post: function (path, params, callback) {
  128. var data = params.data;
  129. delete params.data;
  130. // Sign without multipart data
  131. var r = request.post(baseURL + path, function (err, response, body) {
  132. try { body = JSON.parse(body); } catch (e) { body = { error: 'Malformed Response: ' + body }; }
  133. requestCallback(callback)(err, response, body);
  134. });
  135. // Sign it with the non-data parameters
  136. r.form(params);
  137. r.oauth(this.credentials);
  138. // Clear the side effects from form(param)
  139. delete r.headers['content-type'];
  140. delete r.body;
  141. // And then add the full body
  142. var form = r.form();
  143. for (var key in params) {
  144. form.append(key, params[key]);
  145. }
  146. if (data) {
  147. form.append('data', fs.createReadStream(data));
  148. }
  149. // Add the form header back
  150. var headers = form.getHeaders();
  151. for (key in headers) {
  152. r.headers[key] = headers[key];
  153. }
  154. }
  155. };
  156. var requireValidation = function (options, choices) {
  157. var count = 0;
  158. for (var i = 0; i < choices.length; i++) {
  159. if (options[choices[i]]) {
  160. count += 1;
  161. }
  162. }
  163. if (choices.length === 1) {
  164. if (count === 0) {
  165. throw new Error('Missing required field: "' + choices[0] + '"');
  166. }
  167. } else if (choices.length > 1) {
  168. if (count === 0) {
  169. throw new Error('Missing one of: ' + choices.join(','));
  170. }
  171. if (count > 1) {
  172. throw new Error('Can only use one of: ' + choices.join(','));
  173. }
  174. }
  175. };
  176. function blogPath(blogName, path) {
  177. var bn = blogName.indexOf('.') !== -1 ? blogName : blogName + '.tumblr.com';
  178. return '/blog/' + bn + path;
  179. }
  180. function requestCallback(callback) {
  181. if (!callback) return undefined;
  182. return function (err, response, body) {
  183. if (err) return callback(err);
  184. if (response.statusCode >= 400) {
  185. var errString = body.meta ? body.meta.msg : body.error;
  186. return callback(new Error('API error: ' + response.statusCode + ' ' + errString));
  187. }
  188. if (body && body.response) {
  189. return callback(null, body.response);
  190. } else {
  191. return callback(new Error('API error (malformed API response): ' + body));
  192. }
  193. };
  194. }
  195. function isFunction(value) {
  196. return Object.prototype.toString.call(value) == '[object Function]';
  197. }