UNPKG

12.6 kBJavaScriptView Raw
1/*!
2 * node-tvdb
3 *
4 * Node.js library for accessing TheTVDB API at <http://www.thetvdb.com/wiki/index.php?title=Programmers_API>
5 *
6 * Copyright (c) 2014-2015 Edward Wellbrook <edwellbrook@gmail.com>
7 * MIT Licensed
8 */
9
10"use strict";
11
12const request = require("request");
13const parser = require("xml2js").parseString;
14const Zip = require("jszip");
15
16// available providers for remote ids
17const REMOTE_PROVIDERS = {
18 imdbid: /^tt/i,
19 zap2it: /^ep/i
20};
21
22// options for xml2js parser
23const PARSER_OPTS = {
24 trim: true,
25 normalize: true,
26 ignoreAttrs: true,
27 explicitArray: false,
28 emptyTag: null
29};
30
31// available response types
32const RESPONSE_TYPE = {
33 XML: 0,
34 ZIP: 1
35};
36
37//
38// API Client
39//
40
41class Client {
42
43 /**
44 * Set up tvdb client with API key and optional language (defaults to "en")
45 *
46 * @param {String} token
47 * @param {String} [language]
48 * @api public
49 */
50
51 constructor(token, language) {
52 if (!token) throw new Error("Access token must be set.");
53
54 this.token = token;
55 this.language = language || "en";
56 this.baseURL = "http://www.thetvdb.com/api";
57 }
58
59 /**
60 * Get available languages useable by TheTVDB API
61 *
62 * http://www.thetvdb.com/wiki/index.php?title=API:languages.xml
63 *
64 * @param {Function} [callback]
65 * @return {Promise} promise
66 * @api public
67 */
68
69 getLanguages(callback) {
70 const url = `${this.baseURL}/${this.token}/languages.xml`;
71
72 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
73 done((response && response.Languages) ? response.Languages.Language : null);
74 }, callback);
75 }
76
77 /**
78 * Get the current server time
79 *
80 * @param {Function} [callback]
81 * @return {Promise} promise
82 * @api public
83 */
84
85 getTime(callback) {
86 const url = `${this.baseURL}/Updates.php?type=none`;
87
88 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
89 done((response && response.Items) ? response.Items.Time : null);
90 }, callback);
91 }
92
93 /**
94 * Get basic series information by name
95 *
96 * http://www.thetvdb.com/wiki/index.php?title=API:GetSeries
97 *
98 * @param {String} name
99 * @param {Function} [callback]
100 * @return {Promise} promise
101 * @api public
102 */
103
104 getSeriesByName(name, callback) {
105 const url = `${this.baseURL}/GetSeries.php?seriesname=${encodeURIComponent(name)}&language=${this.language}`;
106
107 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
108 response = (response && response.Data) ? response.Data.Series : null;
109 done(!response || Array.isArray(response) ? response : [response]);
110 }, callback);
111 }
112
113 /**
114 * Get basic series information by id
115 *
116 * http://www.thetvdb.com/wiki/index.php?title=API:Base_Series_Record
117 *
118 * @param {Number|String} id
119 * @param {Function} [callback]
120 * @return {Promise} promise
121 * @api public
122 */
123
124 getSeriesById(id, callback) {
125 const url = `${this.baseURL}/${this.token}/series/${id}/${this.language}.xml`;
126
127 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
128 done((response && response.Data) ? response.Data.Series : null);
129 }, callback);
130 }
131
132 /**
133 * Get basic series information by remote id (zap2it or imdb)
134 *
135 * http://www.thetvdb.com/wiki/index.php?title=API:GetSeriesByRemoteID
136 *
137 * @param {String} remoteId
138 * @param {Function} [callback]
139 * @return {Promise} promise
140 * @api public
141 */
142
143 getSeriesByRemoteId(remoteId, callback) {
144 const keys = Object.keys(REMOTE_PROVIDERS);
145
146 let provider = "";
147 let len = keys.length;
148
149 while (len-- && provider === "") {
150 if (REMOTE_PROVIDERS[keys[len]].exec(remoteId)) {
151 provider = keys[len];
152 }
153 }
154
155 const url = `${this.baseURL}/GetSeriesByRemoteID.php?${provider}=${remoteId}&language=${this.language}`;
156
157 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
158 done((response && response.Data) ? response.Data.Series : null);
159 }, callback);
160 }
161
162 /**
163 * Get full/all series information by id
164 *
165 * http://www.thetvdb.com/wiki/index.php?title=API:Full_Series_Record
166 *
167 * @param {Number|String} id
168 * @param {Function} [callback]
169 * @return {Promise} promise
170 * @api public
171 */
172
173 getSeriesAllById(id, callback) {
174 const url = `${this.baseURL}/${this.token}/series/${id}/all/${this.language}.zip`;
175
176 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.ZIP, function(response, done) {
177 if (response && response.Data && response.Data.Series) {
178 response.Data.Series.Episodes = response.Data.Episode;
179 }
180
181 done(response ? response.Data.Series : null);
182 }, callback);
183 }
184
185 /**
186 * Get all episodes by series id
187 *
188 * http://www.thetvdb.com/wiki/index.php?title=API:Full_Series_Record
189 *
190 * @param {Number|String} id
191 * @param {Function} [callback]
192 * @return {Promise} promise
193 * @api public
194 */
195
196 getEpisodesById(id, callback) {
197 const url = `${this.baseURL}/api/${this.token}/series/${id}/all/${this.language}.xml`;
198
199 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
200 response = (response && response.Data) ? response.Data.Episode : null;
201 done(!response || Array.isArray(response) ? response : [response]);
202 }, callback);
203 }
204
205 /**
206 * Get episode by episode id
207 *
208 * http://www.thetvdb.com/wiki/index.php?title=API:Base_Episode_Record
209 *
210 * @param {Number|String} id
211 * @param {Function} [callback]
212 * @return {Promise} promise
213 * @api public
214 */
215
216 getEpisodeById(id, callback) {
217 const url = `${this.baseURL}/${this.token}/episodes/${id}/${this.language}.xml`;
218
219 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
220 done((response && response.Data) ? response.Data.Episode : null);
221 }, callback);
222 }
223
224 /**
225 * Get episode by air date
226 *
227 * http://www.thetvdb.com/wiki/index.php?title=API:GetEpisodeByAirDate
228 *
229 * @param {Number|String} seriesId
230 * @param {String} airDate
231 * @param {Function} [callback]
232 * @return {Promise} promise
233 * @api public
234 */
235
236 getEpisodeByAirDate(seriesId, airDate, callback) {
237 const url = `${this.baseURL}/GetEpisodeByAirDate.php?apikey=${this.token}&seriesid=${seriesId}&airdate=${airDate}&language=${this.language}`;
238
239 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
240 done((response && response.Data) ? response.Data.Episode : null);
241 }, callback);
242 }
243
244 /**
245 * Get series actors by series id
246 *
247 * http://www.thetvdb.com/wiki/index.php?title=API:actors.xml
248 *
249 * @param {Number|String} id
250 * @param {Function} [callback]
251 * @return {Promise} promise
252 * @api public
253 */
254
255 getActors(id, callback) {
256 const url = `${this.baseURL}/${this.token}/series/${id}/actors.xml`;
257
258 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
259 done((response && response.Actors) ? response.Actors.Actor : null);
260 }, callback);
261 }
262
263 /**
264 * Get series banners by series id
265 *
266 * http://www.thetvdb.com/wiki/index.php?title=API:banners.xml
267 *
268 * @param {Number|String} id
269 * @param {Function} [callback]
270 * @return {Promise} promise
271 * @api public
272 */
273
274 getBanners(id, callback) {
275 const url = `${this.baseURL}/${this.token}/series/${id}/banners.xml`;
276
277 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
278 done((response && response.Banners) ? response.Banners.Banner : null);
279 }, callback);
280 }
281
282 /**
283 * Get series and episode updates since a given unix timestamp
284 *
285 * http://www.thetvdb.com/wiki/index.php?title=API:Updates
286 *
287 * @param {Number} time
288 * @param {Function} [callback]
289 * @return {Promise} promise
290 * @api public
291 */
292
293 getUpdates(time, callback) {
294 const url = `${this.baseURL}/Updates.php?type=all&time=${time}`;
295
296 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
297 done(response ? response.Items : null);
298 }, callback);
299 }
300
301 /**
302 * All updates within the given interval
303 *
304 * http://www.thetvdb.com/wiki/index.php?title=API:Update_Records
305 *
306 * @param {String} interval - day|week|month|all
307 * @param {Function} [callback]
308 * @return {Promise} promise
309 * @api public
310 */
311
312 getUpdateRecords(interval, callback) {
313 const url = `${this.baseURL}/${this.token}/updates/updates_${interval}.xml`;
314
315 return sendRequest({url: url, lang: this.language}, RESPONSE_TYPE.XML, function(response, done) {
316 done(response ? response.Data : null);
317 }, callback);
318 }
319}
320
321//
322// Utilities
323//
324
325/**
326 * Check if http response is okay to use
327 *
328 * @param {Error} error
329 * @param {Object} resp - request library response object
330 * @param {String|Buffer} data - body/data of response
331 * @return {Boolean} responseOk
332 * @api private
333 */
334
335function responseOk(error, resp, data) {
336 if (error) return false;
337 if (!resp) return false;
338 if (resp.statusCode !== 200) return false;
339 if (!data) return false;
340
341 // if dealing with zip data buffer is okay
342 if (data instanceof Buffer) return true;
343
344 if (data === "") return false;
345 if (data.indexOf("404 Not Found") !== -1) return false;
346
347 return true;
348}
349
350/**
351 * Send and handle http request
352 *
353 * @param {String} url
354 * @param {Number} responseType - response type from RESPONSE_TYPE
355 * @param {Function} normalise - a function to tidy the response object
356 * @param {Function} [callback]
357 * @return {Promise} promise
358 * @api private
359 */
360
361function sendRequest(urlOpts, responseType, normalise, callback) {
362 return new Promise(function(resolve, reject) {
363 function done(error, results) {
364 if (callback) {
365 callback(error, results);
366 } else {
367 error ? reject(error) : resolve(results);
368 }
369 }
370
371 let reqOpts = {url: urlOpts.url};
372 if (responseType === RESPONSE_TYPE.ZIP) {
373 reqOpts.encoding = null;
374 }
375
376 request(reqOpts, function(error, resp, data) {
377 if (!responseOk(error, resp, data)) {
378 if (!error) {
379 error = new Error("Could not complete the request");
380 }
381 error.statusCode = resp ? resp.statusCode : undefined;
382
383 return done(error);
384 } else if (error) {
385 return done(error);
386 }
387
388 if (responseType === RESPONSE_TYPE.ZIP) {
389
390 Zip.loadAsync(data).then(function(zip) {
391 return zip.file(`${urlOpts.lang}.xml`).async("string")
392 }).then(function(contents) {
393 parseXML(contents, normalise, done);
394 }).catch(function(error) {
395 done(error);
396 });
397
398 } else {
399 parseXML(data, normalise, done);
400 }
401 });
402 });
403}
404
405/**
406 * Parse XML response
407 *
408 * @param {String} xml data
409 * @param {Function} normalise - a function to tidy the response object
410 * @param {Function} callback
411 * @api private
412 */
413
414function parseXML(data, normalise, callback) {
415 parser(data, PARSER_OPTS, function(error, results) {
416 if (results && results.Error) {
417 return callback(new Error(results.Error));
418 }
419
420 normalise(results, function(results) {
421 callback(error, results);
422 });
423 });
424}
425
426/**
427 * Parse pipe list string to javascript array
428 *
429 * @param {String} list
430 * @return {Array} parsed list
431 * @api public
432 */
433
434function parsePipeList(list) {
435 return list.replace(/(^\|)|(\|$)/g, "").split("|");
436}
437
438//
439// Exports
440//
441
442Client.utils = {
443 parsePipeList: parsePipeList
444};
445
446module.exports = Client;