joke.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. const http = require("http");
  2. const convertFileFormat = require("../src/fileFormatConverter");
  3. const httpServer = require("../src/httpServer");
  4. const parseURL = require("../src/parseURL");
  5. const parseJokes = require("../src/parseJokes");
  6. const languages = require("../src/languages");
  7. const tr = require("../src/translate");
  8. const FilteredJoke = require("../src/classes/FilteredJoke");
  9. const jsl = require("svjsl");
  10. const settings = require("../settings");
  11. jsl.unused(http);
  12. const meta = {
  13. "name": "Joke",
  14. "desc": "Returns a joke from the specified category / categories that is also matching the provided (optional) filters",
  15. "usage": {
  16. "method": "GET",
  17. "url": `${settings.info.docsURL}/joke/{CATEGORY}`,
  18. "supportedParams": [
  19. "safe-mode",
  20. "format",
  21. "blacklistFlags",
  22. "type",
  23. "contains",
  24. "idRange",
  25. "lang",
  26. "amount"
  27. ]
  28. }
  29. };
  30. /**
  31. * Calls this endpoint
  32. * @param {http.IncomingMessage} req The HTTP server request
  33. * @param {http.ServerResponse} res The HTTP server response
  34. * @param {Array<String>} url URL path array gotten from the URL parser module
  35. * @param {Object} params URL query params gotten from the URL parser module
  36. * @param {String} format The file format to respond with
  37. */
  38. const call = (req, res, url, params, format) => {
  39. jsl.unused([req, url]);
  40. let filterJoke = new FilteredJoke(parseJokes.allJokes);
  41. //#SECTION category validation
  42. let category = (url[settings.httpServer.urlPathOffset + 1]|| "(empty)").toLowerCase() || "";
  43. let includesSplitChar = false;
  44. settings.jokes.splitChars.forEach(splC => {
  45. if(!jsl.isEmpty(category) && category.includes(splC))
  46. includesSplitChar = true;
  47. });
  48. if(includesSplitChar)
  49. category = category.split(settings.jokes.splitCharRegex);
  50. // resolve category aliases
  51. if(Array.isArray(category))
  52. category = parseJokes.resolveCategoryAliases(category);
  53. else
  54. category = parseJokes.resolveCategoryAlias(category);
  55. let categoryValid = false;
  56. [settings.jokes.possible.anyCategoryName, ...settings.jokes.possible.categories].forEach(cat => {
  57. if(typeof category == "string")
  58. {
  59. if(category.toLowerCase() == cat.toLowerCase())
  60. categoryValid = true;
  61. }
  62. else if(Array.isArray(category))
  63. {
  64. if(category.map(c => c.toLowerCase()).includes(cat.toLowerCase()))
  65. categoryValid = true;
  66. }
  67. });
  68. let fCat = false;
  69. if(!Array.isArray(category))
  70. fCat = filterJoke.setAllowedCategories([category]);
  71. else fCat = filterJoke.setAllowedCategories(category);
  72. let langCode = settings.languages.defaultLanguage;
  73. //#SECTION language
  74. if(params && !jsl.isEmpty(params["lang"]))
  75. {
  76. try
  77. {
  78. langCode = params["lang"].toString();
  79. if(languages.isValidLang(langCode) === true)
  80. filterJoke.setLanguage(langCode);
  81. else
  82. return isErrored(res, format, tr(langCode, "invalidLangCode", langCode), langCode);
  83. }
  84. catch(err)
  85. {
  86. return isErrored(res, format, tr(langCode, "invalidLangCodeNoArg"), langCode);
  87. }
  88. }
  89. //#SECTION safe mode
  90. if(params && !jsl.isEmpty(params["safe-mode"]) && params["safe-mode"] === true)
  91. filterJoke.setSafeMode(true);
  92. if(!fCat || !categoryValid)
  93. {
  94. let avlCats = [settings.jokes.possible.anyCategoryName, ...settings.jokes.possible.categories].join(", ");
  95. let catName = category.length == undefined || typeof category != "object" ? category : category.join(", ");
  96. return isErrored(res, format, tr(langCode, "invalidCategory", catName, avlCats), langCode);
  97. }
  98. let jokeAmount = 1;
  99. if(!jsl.isEmpty(params))
  100. {
  101. //#SECTION type
  102. if(!jsl.isEmpty(params["type"]) && settings.jokes.possible.types.map(t => t.toLowerCase()).includes(params["type"].toLowerCase()))
  103. {
  104. if(!filterJoke.setAllowedType(params["type"].toLowerCase()))
  105. return isErrored(res, format, tr(langCode, "invalidType", params["type"], settings.jokes.possible.types.join(", ")), langCode);
  106. }
  107. //#SECTION contains
  108. if(!jsl.isEmpty(params["contains"]))
  109. {
  110. if(!filterJoke.setSearchString(params["contains"].toLowerCase()))
  111. return isErrored(res, format, tr(langCode, "invalidType", params["type"], settings.jokes.possible.types.join(", ")), langCode);
  112. }
  113. //#SECTION idRange
  114. if(!jsl.isEmpty(params["idRange"]))
  115. {
  116. try
  117. {
  118. if(params["idRange"].match(settings.jokes.splitCharRegex))
  119. {
  120. let splitParams = params["idRange"].split(settings.jokes.splitCharRegex);
  121. if(!splitParams[0] && splitParams[1])
  122. splitParams[0] = splitParams[1];
  123. if(!splitParams[1] && splitParams[0])
  124. splitParams[1] = splitParams[0];
  125. if(!filterJoke.setIdRange(parseInt(splitParams[0]), parseInt(splitParams[1])))
  126. return isErrored(res, format, tr(langCode, "idRangeInvalid", splitParams[0], splitParams[1], (parseJokes.jokeCountPerLang[langCode] - 1)), langCode);
  127. }
  128. else
  129. {
  130. let id = parseInt(params["idRange"]);
  131. if(!filterJoke.setIdRange(id, id, langCode))
  132. return isErrored(res, format, tr(langCode, "idRangeInvalidSingle", params["idRange"], (parseJokes.jokeCountPerLang[langCode] - 1)), langCode);
  133. }
  134. }
  135. catch(err)
  136. {
  137. return isErrored(res, format, tr(langCode, "idRangeInvalidGeneric", err), langCode);
  138. }
  139. }
  140. //#SECTION blacklistFlags
  141. if(!jsl.isEmpty(params["blacklistFlags"]))
  142. {
  143. let flags = params["blacklistFlags"].split(settings.jokes.splitCharRegex) || [];
  144. let erroredFlags = [];
  145. flags.forEach(fl => {
  146. if(!settings.jokes.possible.flags.includes(fl))
  147. erroredFlags.push(fl);
  148. });
  149. if(erroredFlags.length > 0)
  150. return isErrored(res, format, tr(langCode, "invalidFlags", flags.join(", "), settings.jokes.possible.flags.join(", ")), langCode);
  151. let fFlg = filterJoke.setBlacklistFlags(flags);
  152. if(!fFlg)
  153. return isErrored(res, format, tr(langCode, "invalidFlags", flags.join(", "), settings.jokes.possible.flags.join(", ")), langCode);
  154. }
  155. //#SECTION amount
  156. if(!jsl.isEmpty(params["amount"]))
  157. {
  158. jokeAmount = parseInt(params["amount"]);
  159. if(isNaN(jokeAmount) || jokeAmount < 1)
  160. jokeAmount = 1;
  161. if(jokeAmount > settings.jokes.maxAmount)
  162. jokeAmount = settings.jokes.maxAmount;
  163. let fAmt = filterJoke.setAmount(jokeAmount);
  164. if(!fAmt)
  165. return isErrored(res, format, tr(langCode, "amountInternalError", fAmt), langCode);
  166. }
  167. }
  168. filterJoke.getJokes(filterJoke.getAmount()).then(jokesArray => {
  169. let responseText = "";
  170. if(jokeAmount == 1)
  171. {
  172. let singleObj = {
  173. error: false,
  174. ...jokesArray[0]
  175. };
  176. responseText = convertFileFormat.auto(format, singleObj, langCode);
  177. }
  178. else
  179. {
  180. let multiObj = {};
  181. if(format != "xml")
  182. {
  183. multiObj = {
  184. error: false,
  185. amount: (jokesArray.length || 1),
  186. jokes: jokesArray
  187. };
  188. }
  189. else
  190. {
  191. multiObj = {
  192. error: false,
  193. amount: (jokesArray.length || 1),
  194. jokes: { "joke": jokesArray }
  195. };
  196. }
  197. responseText = convertFileFormat.auto(format, multiObj, langCode);
  198. }
  199. if(jokeAmount > settings.jokes.encodeAmount)
  200. httpServer.tryServeEncoded(req, res, responseText, parseURL.getMimeTypeFromFileFormatString(format));
  201. else
  202. httpServer.pipeString(res, responseText, parseURL.getMimeTypeFromFileFormatString(format));
  203. }).catch(err => {
  204. return isErrored(res, format, tr(langCode, "errorWhileFinalizing", Array.isArray(err) ? err.join("; ") : err), langCode);
  205. });
  206. };
  207. /**
  208. * Responds with a preformatted error message
  209. * @param {http.ServerResponse} res
  210. * @param {String} format
  211. * @param {String} msg
  212. * @param {String} lang 2-char lang code
  213. * @param {...any} args Arguments to replace numbered %-placeholders with. Only use objects that are strings or convertable to them with `.toString()`!
  214. */
  215. const isErrored = (res, format, msg, lang, ...args) => {
  216. let errFromRegistry = require("." + settings.errors.errorMessagesPath)["106"];
  217. let errorObj = {};
  218. let insArgs = (texts, insertions) => {
  219. if(!Array.isArray(insertions) || insertions.length <= 0)
  220. return texts;
  221. insertions.forEach((ins, i) => {
  222. if(Array.isArray(texts))
  223. texts = texts.map(tx => tx.replace(`%${i + 1}`, ins));
  224. else if(typeof texts == "string")
  225. texts = texts.replace(`%${i + 1}`, ins);
  226. });
  227. return texts;
  228. };
  229. if(format != "xml")
  230. {
  231. errorObj = {
  232. error: true,
  233. internalError: false,
  234. code: 106,
  235. message: insArgs(errFromRegistry.errorMessage[lang], args) || insArgs(errFromRegistry.errorMessage[settings.languages.defaultLanguage], args),
  236. causedBy: insArgs(errFromRegistry.causedBy[lang], args) || insArgs(errFromRegistry.causedBy[settings.languages.defaultLanguage], args),
  237. additionalInfo: msg,
  238. timestamp: new Date().getTime()
  239. };
  240. }
  241. else if(format == "xml")
  242. {
  243. errorObj = {
  244. error: true,
  245. internalError: false,
  246. code: 106,
  247. message: insArgs(errFromRegistry.errorMessage[lang], args) || insArgs(errFromRegistry.errorMessage[settings.languages.defaultLanguage], args),
  248. causedBy: {"cause": insArgs(errFromRegistry.causedBy[lang], args) || insArgs(errFromRegistry.causedBy[settings.languages.defaultLanguage], args)},
  249. additionalInfo: msg,
  250. timestamp: new Date().getTime()
  251. };
  252. }
  253. let responseText = convertFileFormat.auto(format, errorObj, lang);
  254. httpServer.pipeString(res, responseText, parseURL.getMimeTypeFromFileFormatString(format), 400);
  255. };
  256. module.exports = { meta, call };