tvdb.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. const settings = require('../data/settings.json');
  2. const MovieDB = require('moviedb')(settings.tmdbApiKey);
  3. const _ = require('lodash');
  4. const GENRE_TRANSLATIONS = settings.genre.translations;
  5. const GENRE_WEIGHTS = settings.genre.weights;
  6. const GENRE_COMBINATIONS = settings.genre.combinations;
  7. const searchQuery = name => {
  8. let parts = name.match(/(.+?)(\s\((\d{4})\))$/);
  9. let query = (parts && parts.length > 0) ? parts[1] : name;
  10. let year = (parts && parts.length > 2) ? parts[3] : false;
  11. return { query:query, year:year };
  12. };
  13. const searchFilter = (data, year = false) => {
  14. if (!year) return data;
  15. return data.filter(d => d.release_date.toString().includes(year.toString()));
  16. };
  17. const searchMovie = name => {
  18. const { query, year } = searchQuery(name);
  19. return new Promise((resolve, reject) => {
  20. MovieDB.searchMovie({ query: query }, (err, res) => {
  21. if (err) reject(err);
  22. resolve(searchFilter(res.results,year));
  23. });
  24. });
  25. };
  26. const searchTv = name => {
  27. const { query, year } = searchQuery(name);
  28. return new Promise((resolve, reject) => {
  29. MovieDB.searchMovie({ query: query }, (err, res) => {
  30. if (err) reject(err);
  31. resolve(searchFilter(res.results,year));
  32. });
  33. });
  34. };
  35. const infoMovie = id => {
  36. return new Promise((resolve, reject) => {
  37. MovieDB.movieInfo({ id: id }, (err, res) => {
  38. if (err) reject(err);
  39. resolve(res);
  40. });
  41. });
  42. };
  43. const infoTv = id => {
  44. return new Promise((resolve, reject) => {
  45. MovieDB.tvInfo({ id: id }, (err, res) => {
  46. if (err) reject(err);
  47. resolve(res);
  48. });
  49. });
  50. };
  51. const genreTranslate = (data = {}) => {
  52. let genres = Object.keys(data);
  53. let translations = Object.keys(GENRE_TRANSLATIONS);
  54. for (let i=0, l=translations.length; i<l; i++) {
  55. let to = translations[i];
  56. let froms = GENRE_TRANSLATIONS[to];
  57. for (let j=0, c=froms.length; j<c; j++) {
  58. let from = froms[j];
  59. if (data[from]) { // if we have the from ... translate
  60. if (!data[to]) data[to] = 0; // init the to value
  61. data[to] += data[from]; // translate value and add value
  62. delete data[from]; // delete the orig
  63. }
  64. }
  65. }
  66. let combinations = Object.keys(GENRE_COMBINATIONS);
  67. for (let i=0, l=combinations.length; i<l; i++) {
  68. let to = combinations[i]; // new to combination
  69. let froms = GENRE_COMBINATIONS[to]; // get the list of things to combine
  70. // check if we have all the genre intersections with the froms
  71. if (_.intersection(genres,froms).length === froms.length) {
  72. if (!data[to]) data[to] = 0; // init the to combination value
  73. for (let j=0, c=froms.length; j<c; j++) {
  74. let from = froms[j];
  75. if (data[from]) { // if we have the from ... translate
  76. data[to] += data[from];
  77. delete data[from]; // delete the orig
  78. }
  79. }
  80. }
  81. }
  82. };
  83. const genreTally = (data = {}, info = {}) => {
  84. let genres = info && info.genres ? info.genres.map(g => g.name) : [];
  85. for (let i = 0, l = genres.length; i < l; i++) {
  86. let genre = genres[i];
  87. if (!data) data = {};
  88. if (!data[genre]) data[genre] = 0;
  89. data[genre] = data[genre] + 1;
  90. }
  91. genreTranslate(data);
  92. };
  93. const genreSorted = data => {
  94. if (!data) return [];
  95. let keys = Object.keys(data);
  96. let values = Object.values(data);
  97. // get a distinctset of values
  98. let distinctValues = [...new Set(values)];
  99. // sort the keys based on current values
  100. let sorted = keys.sort((a, b) => data[b] - data[a]);
  101. // only one key ... return
  102. if (keys.length === 1) return sorted;
  103. // we have more than 1 distinct value
  104. if (distinctValues.length > 1) return sorted;
  105. // we have one entry and all genere are equally weighted ... apply boosting
  106. let genreWeightKeys = Object.keys(GENRE_WEIGHTS); // get the key list
  107. for (let i=0, l=genreWeightKeys.length; i<l; i++) { // loop the list
  108. let genreWeightKey = genreWeightKeys[i]; // pointer to the key name
  109. if (genreWeightKey && keys && keys.includes(genreWeightKey)) // we have a match
  110. data[genreWeightKey] += GENRE_WEIGHTS[genreWeightKey]; // boost
  111. }
  112. // re-sort boosted values
  113. return keys.sort((a, b) => data[b] - data[a]);
  114. };
  115. const genrePrimary = data => {
  116. if (!data) return 'Unknown';
  117. let sorted = genreSorted(data);
  118. return sorted[0];
  119. };
  120. const genreMovie = async (name, list = false) => {
  121. let results = await searchMovie(name);
  122. let genres = {};
  123. for (let i = 0, l = results.length; i < l; i++) {
  124. let result = results[i];
  125. let info = await infoMovie(result.id);
  126. genreTally(genres, info);
  127. }
  128. return (list) ? genreSorted(genres) : genrePrimary(genres);
  129. };
  130. const genreTv = async (name, list = false) => {
  131. let results = await searchTv(name);
  132. let genres = {};
  133. for (let i = 0, l = results.length; i < l; i++) {
  134. let result = results[i];
  135. let info = await infoTv(result.id);
  136. genreTally(genres, info);
  137. }
  138. return (list) ? genreSorted(genres) : genrePrimary(genres);
  139. };
  140. const search = async (name, type = 'movie') => {
  141. if (!name) return new Error('nothing to search');
  142. switch (type) {
  143. case 'tv':
  144. return await searchTv(name);
  145. case 'movie':
  146. return await searchMovie(name);
  147. default:
  148. return new Error('invalid type specified');
  149. }
  150. };
  151. const info = async (id, type = 'movie') => {
  152. if (!id) return new Error('no id specified');
  153. switch (type) {
  154. case 'tv':
  155. return await infoTv(id);
  156. case 'movie':
  157. return await infoMovie(id);
  158. default:
  159. return new Error('invalid type specified');
  160. }
  161. };
  162. const genre = async (name, type = 'movie') => {
  163. if (!name) return new Error('nothing to search');
  164. switch (type) {
  165. case 'tv':
  166. return await genreTv(name);
  167. case 'movie':
  168. return await genreMovie(name);
  169. default:
  170. return new Error('invalid type specified');
  171. }
  172. };
  173. const genres = async (name, type = 'movie') => {
  174. if (!name) return new Error('nothing to search');
  175. switch (type) {
  176. case 'tv':
  177. return await genreTv(name, true);
  178. case 'movie':
  179. return await genreMovie(name, true);
  180. default:
  181. return new Error('invalid type specified');
  182. }
  183. };
  184. module.exports = {
  185. search,
  186. info,
  187. genre,
  188. genres,
  189. };