url.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import URI from 'urijs';
  2. import log from '../log.js';
  3. import { settings_api } from '../shared/settings/api.js';
  4. import { URL_PARSE_OPTIONS } from '../shared/constants.js';
  5. const settings = settings_api;
  6. /**
  7. * Will return false if URL is malformed or contains disallowed characters
  8. * @param {string} text
  9. * @returns {boolean}
  10. */
  11. export function isValidURL (text) {
  12. try {
  13. return !!(new URL(text));
  14. } catch {
  15. return false;
  16. }
  17. }
  18. /**
  19. * @param {string|URI} url
  20. */
  21. export function getURI (url) {
  22. try {
  23. return url instanceof URI ? url : new URI(url);
  24. } catch (error) {
  25. log.debug(error);
  26. return null;
  27. }
  28. }
  29. /**
  30. * Given the an array of file extensions, check whether a URL points to a file
  31. * ending in one of them.
  32. * @param {string[]} types - An array of file extensions
  33. * @param {string} url
  34. * @returns {boolean}
  35. * @example
  36. * checkFileTypes(['.gif'], 'https://conversejs.org/cat.gif?foo=bar');
  37. */
  38. export function checkFileTypes (types, url) {
  39. const uri = getURI(url);
  40. if (uri === null) {
  41. throw new Error(`checkFileTypes: could not parse url ${url}`);
  42. }
  43. const filename = uri.filename().toLowerCase();
  44. return !!types.filter(ext => filename.endsWith(ext)).length;
  45. }
  46. export function filterQueryParamsFromURL (url) {
  47. const paramsArray = settings.get('filter_url_query_params');
  48. if (!paramsArray) return url;
  49. const parsed_uri = getURI(url);
  50. return parsed_uri.removeQuery(paramsArray).toString();
  51. }
  52. export function isURLWithImageExtension (url) {
  53. return checkFileTypes(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.svg'], url);
  54. }
  55. export function isGIFURL (url) {
  56. return checkFileTypes(['.gif'], url);
  57. }
  58. export function isAudioURL (url) {
  59. return checkFileTypes(['.ogg', '.mp3', '.m4a'], url);
  60. }
  61. export function isVideoURL (url) {
  62. return checkFileTypes(['.mp4', '.webm'], url);
  63. }
  64. export function isImageURL (url) {
  65. const regex = settings.get('image_urls_regex');
  66. return regex?.test(url) || isURLWithImageExtension(url);
  67. }
  68. export function isEncryptedFileURL (url) {
  69. return url.startsWith('aesgcm://');
  70. }
  71. /**
  72. * @typedef {Object} MediaURLMetadata
  73. * An object representing the metadata of a URL found in a chat message
  74. * The actual URL is not saved, it can be extracted via the `start` and `end` indexes.
  75. * @property {boolean} [is_audio]
  76. * @property {boolean} [is_image]
  77. * @property {boolean} [is_video]
  78. * @property {boolean} [is_encrypted]
  79. * @property {number} [end]
  80. * @property {number} [start]
  81. */
  82. /**
  83. * An object representing a URL found in a chat message
  84. * @typedef {MediaURLMetadata} MediaURLData
  85. * @property {string} url
  86. */
  87. /**
  88. * @param {string} text
  89. * @param {number} offset
  90. * @returns {{media_urls?: MediaURLMetadata[]}}
  91. */
  92. export function getMediaURLsMetadata (text, offset=0) {
  93. const objs = [];
  94. if (!text) {
  95. return {};
  96. }
  97. try {
  98. URI.withinString(
  99. text,
  100. (url, start, end) => {
  101. if (url.startsWith('_')) {
  102. url = url.slice(1);
  103. start += 1;
  104. }
  105. if (url.endsWith('_')) {
  106. url = url.slice(0, url.length-1);
  107. end -= 1;
  108. }
  109. objs.push({ url, 'start': start+offset, 'end': end+offset });
  110. return url;
  111. },
  112. URL_PARSE_OPTIONS
  113. );
  114. } catch (error) {
  115. log.debug(error);
  116. }
  117. const media_urls = objs
  118. .map(o => ({
  119. 'end': o.end,
  120. 'is_audio': isAudioURL(o.url),
  121. 'is_image': isImageURL(o.url),
  122. 'is_video': isVideoURL(o.url),
  123. 'is_encrypted': isEncryptedFileURL(o.url),
  124. 'start': o.start
  125. }));
  126. return media_urls.length ? { media_urls } : {};
  127. }
  128. /**
  129. * Given an array of {@link MediaURLMetadata} objects and text, return an
  130. * array of {@link MediaURL} objects.
  131. * @param {Array<MediaURLMetadata>} arr
  132. * @param {string} text
  133. * @returns {MediaURLData[]}
  134. */
  135. export function getMediaURLs (arr, text, offset=0) {
  136. return arr.map(o => {
  137. const start = o.start - offset;
  138. const end = o.end - offset;
  139. if (start < 0 || start >= text.length) {
  140. return null;
  141. }
  142. return (Object.assign({}, o, {
  143. start,
  144. end,
  145. 'url': text.substring(o.start-offset, o.end-offset),
  146. }));
  147. }).filter(o => o);
  148. }