1 |
|
2 | import https from 'https';
|
3 | import querystring from 'querystring';
|
4 | import Promise from 'bluebird';
|
5 | import logger from '../lib/logger';
|
6 |
|
7 | class InstagramSDK {
|
8 | instagramHost = 'api.instagram.com';
|
9 | apiPath = '/v1';
|
10 |
|
11 | constructor({clientID = null, clientSecret = null, accessToken = null} = {}) {
|
12 |
|
13 | if(!clientID && !clientSecret && !accessToken) {
|
14 | throw new Error('You must specify on of: `accessToken` or both `clientID` and `clientSecret`.');
|
15 | }
|
16 |
|
17 | this.clientID = clientID;
|
18 | this.clientSecret = clientSecret;
|
19 | this.accessToken = accessToken;
|
20 |
|
21 | if(!accessToken && (clientID && clientSecret)) {
|
22 | this.printLoginUrl();
|
23 | } else if(accessToken) {
|
24 |
|
25 | }
|
26 | }
|
27 |
|
28 | printLoginUrl() {
|
29 | console.log(`https://${this.instagramHost}/oauth/authorize/?client_id=${this.clientID}&redirect_uri=http://poster.loc&response_type=code`);
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | getSelf() {
|
37 | return this._request().then(this._parseJSON);
|
38 | }
|
39 |
|
40 | getSelfRecentMedia({count = 10, min_id = undefined, max_id = undefined} = {}) {
|
41 | return this._request({path: '/users/self/media/recent', query: {count, min_id, max_id}}).then(this._parseJSON);
|
42 | }
|
43 |
|
44 | getSelfRecentLikes({count = 10, max_like_id = undefined} = {}) {
|
45 | return this._request({path: '/users/self/media/liked', query: {count, max_like_id}}).then(this._parseJSON);
|
46 | }
|
47 |
|
48 | getUser(userID) {
|
49 | if(!userID) {
|
50 | throw new Error('Argument `userID` is required.');
|
51 | }
|
52 |
|
53 | return this._request({path: '/users/' + userID}).then(this._parseJSON);
|
54 | }
|
55 |
|
56 | getUserRecentMedia(userID, {count = 10, min_id = undefined, max_id = undefined} = {}) {
|
57 | if(!userID) {
|
58 | throw new Error('Argument `userID` is required.');
|
59 | }
|
60 |
|
61 | return this._request({path: `/users/${userID}/media/recent`, query: {count, min_id, max_id}}).then(this._parseJSON);
|
62 | }
|
63 |
|
64 | usersSearch({q = '', count = 10} = {}) {
|
65 | return this._request({path: '/users/search', query: {q, count}}).then(this._parseJSON);
|
66 | }
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | getSelfFollows() {
|
73 | return this._request({path: '/users/self/follows'}).then(this._parseJSON);
|
74 | }
|
75 |
|
76 | getSelfFollowedBy() {
|
77 | return this._request({path: '/users/self/followed-by'}).then(this._parseJSON);
|
78 | }
|
79 |
|
80 | getSelfRequestedBy() {
|
81 | return this._request({path: '/users/self/requested-by'}).then(this._parseJSON);
|
82 | }
|
83 |
|
84 | getUserRelationship(userID) {
|
85 | if(!userID) {
|
86 | throw new Error('Argument `userID` is required.');
|
87 | }
|
88 |
|
89 | return this._request({path: `/users/${userID}/relationship`}).then(this._parseJSON);
|
90 | }
|
91 |
|
92 | updateUserRelationship(userID, action) {
|
93 | if(!userID) {
|
94 | throw new Error('Argument `userID` is required.');
|
95 | }
|
96 |
|
97 | if(!action) {
|
98 | throw new Error('Argument `action` is required.');
|
99 | }
|
100 |
|
101 | return this._request({method: 'POST', path: `/users/${userID}/relationship`, postData: {action}}).then(this._parseJSON);
|
102 | }
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 | getMediaInfoById(mediaID) {
|
109 | if(!mediaID) {
|
110 | throw new Error('Argument `mediaID` is required.');
|
111 | }
|
112 |
|
113 | return this._request({path: `/media/${mediaID}`}).then(this._parseJSON);
|
114 | }
|
115 |
|
116 | getMediaInfoByShortCode(shortCode) {
|
117 | if(!shortCode) {
|
118 | throw new Error('Argument `shortCode` is required.');
|
119 | }
|
120 |
|
121 | return this._request({path: `/media/shortcode/${shortCode}`}).then(this._parseJSON);
|
122 | }
|
123 |
|
124 | mediaSearch({lat = 0, lng = 0, distance = 10} = {}) {
|
125 | return this._request({path: '/media/search', query: {lat, lng, distance}}).then(this._parseJSON);
|
126 | }
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | getCommentsForMedia(mediaID) {
|
133 | if(!mediaID) {
|
134 | throw new Error('Argument `mediaID` is required.');
|
135 | }
|
136 |
|
137 | return this._request({path: `/media/${mediaID}/comments`}).then(this._parseJSON);
|
138 | }
|
139 |
|
140 | addCommentForMedia(mediaID, text) {
|
141 | if(!mediaID) {
|
142 | throw new Error('Argument `mediaID` is required.');
|
143 | }
|
144 |
|
145 | if(!text) {
|
146 | throw new Error('Argument `text` is required.');
|
147 | }
|
148 |
|
149 | return this._request({method: 'POST', path: `/media/${mediaID}/comments`, postData: {text}}).then(this._parseJSON);
|
150 | }
|
151 |
|
152 | removeCommentForMedia(mediaID, commentId) {
|
153 | if(!mediaID) {
|
154 | throw new Error('Argument `mediaID` is required.');
|
155 | }
|
156 |
|
157 | if(!commentId) {
|
158 | throw new Error('Argument `commentId` is required.');
|
159 | }
|
160 |
|
161 | return this._request({method: 'DELETE', path: `/media/${mediaID}/comments/${commentId}`}).then(this._parseJSON);
|
162 | }
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | getLikesForMedia(mediaID) {
|
169 | if(!mediaID) {
|
170 | throw new Error('Argument `mediaID` is required.');
|
171 | }
|
172 |
|
173 | return this._request({path: `/media/${mediaID}/likes`}).then(this._parseJSON);
|
174 | }
|
175 |
|
176 | addLikeForMedia(mediaID) {
|
177 | if(!mediaID) {
|
178 | throw new Error('Argument `mediaID` is required.');
|
179 | }
|
180 |
|
181 | return this._request({method: 'POST', path: `/media/${mediaID}/likes`}).then(this._parseJSON);
|
182 | }
|
183 |
|
184 | removeLikeForMedia(mediaID) {
|
185 | if(!mediaID) {
|
186 | throw new Error('Argument `mediaID` is required.');
|
187 | }
|
188 |
|
189 | return this._request({method: 'DELETE', path: `/media/${mediaID}/likes`}).then(this._parseJSON);
|
190 | }
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 | getTagInfoByTagName(tagName) {
|
197 | if(!tagName) {
|
198 | throw new Error('Argument `tagName` is required.');
|
199 | }
|
200 |
|
201 | return this._request({path: `/tags/${tagName}`}).then(this._parseJSON);
|
202 | }
|
203 |
|
204 | getRecentMediaForTagName(tagName, {count = 10, min_tag_id = undefined, max_tag_id = undefined} = {}) {
|
205 | if(!tagName) {
|
206 | throw new Error('Argument `tagName` is required.');
|
207 | }
|
208 |
|
209 | return this._request({path: `/tags/${tagName}/media/recent`, query: {count, min_tag_id, max_tag_id}}).then(this._parseJSON);
|
210 | }
|
211 |
|
212 | tagsSearch(q = '') {
|
213 | return this._request({path: '/tags/search', query: {q}}).then(this._parseJSON);
|
214 | }
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | getLocationInfoByLocationId(locationId) {
|
221 | if(!locationId) {
|
222 | throw new Error('Argument `locationId` is required.');
|
223 | }
|
224 |
|
225 | return this._request({path: `/locations/${locationId}`}).then(this._parseJSON);
|
226 | }
|
227 |
|
228 | getRecentMediaForLocationId(locationId, {min_tag_id = undefined, max_tag_id = undefined} = {}) {
|
229 | if(!locationId) {
|
230 | throw new Error('Argument `locationId` is required.');
|
231 | }
|
232 |
|
233 | return this._request({path: `/locations/${locationId}/media/recent`, query: {min_tag_id, max_tag_id}}).then(this._parseJSON);
|
234 | }
|
235 |
|
236 | locationsSearch({
|
237 | distance = 1000,
|
238 | facebook_places_id = undefined,
|
239 | foursquare_id = undefined,
|
240 | lat = undefined,
|
241 | lng = undefined,
|
242 | foursquare_v2_id = undefined
|
243 | } = {}) {
|
244 |
|
245 | return this._request({
|
246 | path: '/locations/search',
|
247 | query: {
|
248 | distance,
|
249 | facebook_places_id,
|
250 | foursquare_id,
|
251 | lat,
|
252 | lng,
|
253 | foursquare_v2_id
|
254 | }
|
255 | }).then(this._parseJSON);
|
256 | }
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | getMediaJPGByShortCode(shortCode) {
|
263 | if(!shortCode) {
|
264 | throw new Error('Argument `shortCode` is required.');
|
265 | }
|
266 |
|
267 | return this._request({path: `/p/${shortCode}/media`}).then(this._getHeaderValue('Location'));
|
268 | }
|
269 |
|
270 | _getHeaderValue(headerName) {
|
271 | return ({res} = {}) => {
|
272 | return Promise.resolve(res.headers[headerName]);
|
273 | };
|
274 | }
|
275 |
|
276 | _parseJSON({res, resData} = {}) {
|
277 | console.log('_parseJSON called');
|
278 | var resJSON = {};
|
279 |
|
280 | if(res.statusCode == 204) {
|
281 | return Promise.resolve({});
|
282 | }
|
283 |
|
284 | try {
|
285 | resJSON = JSON.parse(resData);
|
286 | } catch(err) {
|
287 | logger.debug('_parseJSON: invalid response:', resData);
|
288 | return Promise.reject(new Error('Invalid JSON response:' + resData));
|
289 | }
|
290 |
|
291 | logger.debug('_parseJSON: response: %:2j', resJSON);
|
292 | return Promise.resolve(resJSON);
|
293 | }
|
294 |
|
295 | _request({method = 'GET', path = '/users/self', postData, query = {}} = {}) {
|
296 | return new Promise((resolve, reject) => {
|
297 | query.access_token = this.accessToken;
|
298 | let contentType = 'application/json',
|
299 | headers = {
|
300 | Accept: contentType
|
301 | },
|
302 | requestOptions = {
|
303 | hostname: this.instagramHost,
|
304 | path: this.apiPath + path + '?' + querystring.stringify(query),
|
305 | method
|
306 | },
|
307 | _postData;
|
308 |
|
309 | if(postData) {
|
310 | _postData = JSON.stringify(postData);
|
311 | headers['Content-Type'] = 'application/json';
|
312 | headers['Content-length'] = _postData.length;
|
313 | logger.debug('_request: request with postData: %:2j', _postData);
|
314 | }
|
315 |
|
316 | requestOptions.headers = headers;
|
317 | logger.debug('_request: request with params: %:2j', requestOptions);
|
318 |
|
319 | var request = https.request(requestOptions, (res) => {
|
320 | var resData = '';
|
321 |
|
322 | res.on('data', (data) => {
|
323 | resData += data;
|
324 | });
|
325 | res.on('end', () => {
|
326 | logger.debug('_request: response statusCode:', res.statusCode);
|
327 | resolve({res, resData});
|
328 | });
|
329 | }).on('error', reject);
|
330 |
|
331 | if(postData) {
|
332 | request.write(_postData);
|
333 | }
|
334 |
|
335 | request.end();
|
336 | });
|
337 | }
|
338 | }
|
339 |
|
340 | export default InstagramSDK;
|