import URI from 'urijs'; import log from '../log.js'; import { settings_api } from '../shared/settings/api.js'; import { URL_PARSE_OPTIONS } from '../shared/constants.js'; const settings = settings_api; /** * Will return false if URL is malformed or contains disallowed characters * @param {string} text * @returns {boolean} */ export function isValidURL (text) { try { return !!(new URL(text)); } catch { return false; } } /** * @param {string|URI} url */ export function getURI (url) { try { return url instanceof URI ? url : new URI(url); } catch (error) { log.debug(error); return null; } } /** * Given the an array of file extensions, check whether a URL points to a file * ending in one of them. * @param {string[]} types - An array of file extensions * @param {string} url * @returns {boolean} * @example * checkFileTypes(['.gif'], 'https://conversejs.org/cat.gif?foo=bar'); */ export function checkFileTypes (types, url) { const uri = getURI(url); if (uri === null) { throw new Error(`checkFileTypes: could not parse url ${url}`); } const filename = uri.filename().toLowerCase(); return !!types.filter(ext => filename.endsWith(ext)).length; } export function filterQueryParamsFromURL (url) { const paramsArray = settings.get('filter_url_query_params'); if (!paramsArray) return url; const parsed_uri = getURI(url); return parsed_uri.removeQuery(paramsArray).toString(); } export function isURLWithImageExtension (url) { return checkFileTypes(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.svg'], url); } export function isGIFURL (url) { return checkFileTypes(['.gif'], url); } export function isAudioURL (url) { return checkFileTypes(['.ogg', '.mp3', '.m4a'], url); } export function isVideoURL (url) { return checkFileTypes(['.mp4', '.webm'], url); } export function isImageURL (url) { const regex = settings.get('image_urls_regex'); return regex?.test(url) || isURLWithImageExtension(url); } export function isEncryptedFileURL (url) { return url.startsWith('aesgcm://'); } /** * @typedef {Object} MediaURLMetadata * An object representing the metadata of a URL found in a chat message * The actual URL is not saved, it can be extracted via the `start` and `end` indexes. * @property {boolean} [is_audio] * @property {boolean} [is_image] * @property {boolean} [is_video] * @property {boolean} [is_encrypted] * @property {number} [end] * @property {number} [start] */ /** * An object representing a URL found in a chat message * @typedef {MediaURLMetadata} MediaURLData * @property {string} url */ /** * @param {string} text * @param {number} offset * @returns {{media_urls?: MediaURLMetadata[]}} */ export function getMediaURLsMetadata (text, offset=0) { const objs = []; if (!text) { return {}; } try { URI.withinString( text, (url, start, end) => { if (url.startsWith('_')) { url = url.slice(1); start += 1; } if (url.endsWith('_')) { url = url.slice(0, url.length-1); end -= 1; } objs.push({ url, 'start': start+offset, 'end': end+offset }); return url; }, URL_PARSE_OPTIONS ); } catch (error) { log.debug(error); } const media_urls = objs .map(o => ({ 'end': o.end, 'is_audio': isAudioURL(o.url), 'is_image': isImageURL(o.url), 'is_video': isVideoURL(o.url), 'is_encrypted': isEncryptedFileURL(o.url), 'start': o.start })); return media_urls.length ? { media_urls } : {}; } /** * Given an array of {@link MediaURLMetadata} objects and text, return an * array of {@link MediaURL} objects. * @param {Array} arr * @param {string} text * @returns {MediaURLData[]} */ export function getMediaURLs (arr, text, offset=0) { return arr.map(o => { const start = o.start - offset; const end = o.end - offset; if (start < 0 || start >= text.length) { return null; } return (Object.assign({}, o, { start, end, 'url': text.substring(o.start-offset, o.end-offset), })); }).filter(o => o); }