Render audio from URLs in messages

This commit is contained in:
JC Brand 2021-06-17 14:37:43 +02:00
parent 33b426c79e
commit 095d9b60cd
14 changed files with 257 additions and 186 deletions

View File

@ -793,6 +793,17 @@ domain_placeholder
The placeholder text shown in the domain input on the registration form.
embed_audio
-----------
* Default: ``true``
If set to ``false``, audio files won't be embedded in chats, instead only their links will be shown.
It also accepts an array strings of whitelisted domain names to only render videos that belong to those domains.
E.g. ``['conversejs.org']``
embed_videos
------------

View File

@ -47,9 +47,11 @@ module.exports = function(config) {
{ pattern: "src/plugins/chatview/tests/http-file-upload.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/markers.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/me-messages.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-audio.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-images.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-videos.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/messages.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/oob.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/receipts.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/spoilers.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/xss.js", type: 'module' },

View File

@ -37,6 +37,7 @@ converse.plugins.add('converse-chatview', {
'auto_focus': true,
'debounced_content_rendering': true,
'embed_videos': true,
'embed_audio': true,
'filter_url_query_params': null,
'image_urls_regex': null,
'message_limit': 0,

View File

@ -0,0 +1,25 @@
/*global mock, converse */
const { sizzle, u } = converse.env;
describe("A Chat Message", function () {
it("will render audio files from their URLs",
mock.initConverse(['chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current');
const base_url = 'https://conversejs.org';
const message = base_url+"/logo/audio.mp3";
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
await mock.sendMessage(view, message);
await u.waitUntil(() => view.querySelectorAll('.chat-content audio').length, 1000)
const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop();
expect(msg.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="${message}"></audio>`+
`<a target="_blank" rel="noopener" href="${message}">${message}</a>`);
done();
}));
});

View File

@ -1267,167 +1267,4 @@ describe("A Chat Message", function () {
done();
}));
});
describe("which contains an OOB URL", function () {
it("will render audio from oob mp3 URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
let stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you heard this funny audio?</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/audio.mp3</url></x>
</message>`)
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
let msg = view.querySelector('.chat-msg .chat-msg__text');
expect(msg.classList.length).toEqual(1);
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you heard this funny audio?');
let media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="https://montague.lit/audio.mp3"></audio> `+
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">Download audio file "audio.mp3"</a>`);
// If the <url> and <body> contents is the same, don't duplicate.
stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>https://montague.lit/audio.mp3</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/audio.mp3</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you heard this funny audio?'); // Emtpy
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="https://montague.lit/audio.mp3"></audio> `+
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">`+
`Download audio file "audio.mp3"</a>`);
done();
}));
it("will render video from oob mp4 URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
let stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you seen this funny video?</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg video').length, 2000)
let msg = view.querySelector('.chat-msg .chat-msg__text');
expect(msg.classList.length).toBe(1);
expect(msg.textContent).toEqual('Have you seen this funny video?');
let media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
// If the <url> and <body> contents is the same, don't duplicate.
stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>https://montague.lit/video.mp4</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you seen this funny video?');
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
done();
}));
it("will render download links for files from oob URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
const stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you downloaded this funny file?</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/funny.pdf</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.querySelector('.chat-msg .chat-msg__text');
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you downloaded this funny file?');
const media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
`<a target="_blank" rel="noopener" href="https://montague.lit/funny.pdf">Download file "funny.pdf"</a>`);
done();
}));
it("will render images from oob URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
const base_url = 'https://conversejs.org';
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
const url = base_url+"/logo/conversejs-filled.svg";
const stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you seen this funny image?</body>
<x xmlns="jabber:x:oob"><url>${url}</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.querySelector('.chat-msg .chat-msg__text');
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you seen this funny image?');
const media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "")).toEqual(
`<a target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
`Download image file "conversejs-filled.svg"</a>`);
done();
}));
});
});

View File

@ -0,0 +1,167 @@
/*global mock, converse */
const { Promise, u } = converse.env;
describe("A Chat Message", function () {
describe("which contains an OOB URL", function () {
it("will render audio from oob mp3 URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
const url = 'https://montague.lit/audio.mp3';
let stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you heard this funny audio?</body>
<x xmlns="jabber:x:oob"><url>${url}</url></x>
</message>`)
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg audio').length, 1000);
let msg = view.querySelector('.chat-msg .chat-msg__text');
expect(msg.classList.length).toEqual(1);
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you heard this funny audio?');
let media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="https://montague.lit/audio.mp3"></audio>`+
`<a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">${url}</a>`);
// If the <url> and <body> contents is the same, don't duplicate.
stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>https://montague.lit/audio.mp3</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/audio.mp3</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you heard this funny audio?'); // Emtpy
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()).toEqual(
`<audio controls="" src="https://montague.lit/audio.mp3"></audio>`+
`<a target="_blank" rel="noopener" href="${url}">${url}</a>`);
done();
}));
it("will render video from oob mp4 URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
let stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you seen this funny video?</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg video').length, 2000)
let msg = view.querySelector('.chat-msg .chat-msg__text');
expect(msg.classList.length).toBe(1);
expect(msg.textContent).toEqual('Have you seen this funny video?');
let media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
// If the <url> and <body> contents is the same, don't duplicate.
stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>https://montague.lit/video.mp4</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/video.mp4</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
msg = view.querySelector('.chat-msg:last-child .chat-msg__text');
expect(msg.innerHTML.replace(/<!-.*?->/g, '')).toEqual('Have you seen this funny video?');
media = view.querySelector('.chat-msg:last-child .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
`<video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video>`);
done();
}));
it("will render download links for files from oob URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
const stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you downloaded this funny file?</body>
<x xmlns="jabber:x:oob"><url>https://montague.lit/funny.pdf</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.querySelector('.chat-msg .chat-msg__text');
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you downloaded this funny file?');
const media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/<!-.*?->/g, '')).toEqual(
`<a target="_blank" rel="noopener" href="https://montague.lit/funny.pdf">Download file "funny.pdf"</a>`);
done();
}));
it("will render images from oob URLs",
mock.initConverse(
['chatBoxesFetched'], {},
async function (done, _converse) {
const base_url = 'https://conversejs.org';
await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.chatboxviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough();
const url = base_url+"/logo/conversejs-filled.svg";
const stanza = u.toStanza(`
<message from="${contact_jid}"
type="chat"
to="romeo@montague.lit/orchard">
<body>Have you seen this funny image?</body>
<x xmlns="jabber:x:oob"><url>${url}</url></x>
</message>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg a').length, 1000);
const msg = view.querySelector('.chat-msg .chat-msg__text');
expect(u.hasClass('chat-msg__text', msg)).toBe(true);
expect(msg.textContent).toEqual('Have you seen this funny image?');
const media = view.querySelector('.chat-msg .chat-msg__media');
expect(media.innerHTML.replace(/<!-.*?->/g, '').replace(/(\r\n|\n|\r)/gm, "")).toEqual(
`<a target="_blank" rel="noopener" href="${base_url}/logo/conversejs-filled.svg">`+
`Download image file "conversejs-filled.svg"</a>`);
done();
}));
});
});

View File

@ -4,6 +4,8 @@ import renderRichText from 'shared/directives/rich-text.js';
import { CustomElement } from 'shared/components/element.js';
import { api } from "@converse/headless/core";
import './styles/message-body.scss';
export default class MessageBody extends CustomElement {
@ -13,6 +15,7 @@ export default class MessageBody extends CustomElement {
is_me_message: { type: Boolean },
show_images: { type: Boolean },
embed_videos: { type: Boolean },
embed_audio: { type: Boolean },
text: { type: String },
}
}
@ -31,12 +34,13 @@ export default class MessageBody extends CustomElement {
const offset = 0;
const mentions = this.model.get('references');
const options = {
'embed_audio': this.embed_audio,
'embed_videos': this.embed_videos,
'nick': this.model.collection.chatbox.get('nick'),
'onImgClick': this.onImgClick,
'onImgLoad': () => this.onImgLoad(),
'render_styling': !this.model.get('is_unstyled') && api.settings.get('allow_message_styling'),
'show_images': this.show_images,
'embed_videos': this.embed_videos,
'show_me_message': true
}
return renderRichText(this.text, offset, mentions, options, callback);

View File

@ -240,6 +240,7 @@ export default class Message extends CustomElement {
?is_me_message="${this.model.isMeCommand()}"
?show_images="${api.settings.get('show_images_inline')}"
?embed_videos="${api.settings.get('embed_videos')}"
?embed_audio="${api.settings.get('embed_audio')}"
text="${text}"></converse-chat-message-body>
${ (this.model.get('received') && !this.model.isMeCommand() && !is_groupchat_message) ? html`<span class="fa fa-check chat-msg__receipt"></span>` : '' }
${ (this.model.get('edited')) ? html`<i title="${ i18n_edited }" class="fa fa-edit chat-msg__edit-modal" @click=${this.showMessageVersionsModal}></i>` : '' }

View File

@ -0,0 +1,5 @@
converse-chat-message-body {
audio {
width: 100%;
}
}

View File

@ -6,6 +6,8 @@ export default class RichText extends CustomElement {
static get properties () {
return {
embed_audio: { type: Boolean },
embed_videos: { type: Boolean },
mentions: { type: Array },
nick: { type: String },
offset: { type: Number },
@ -13,7 +15,6 @@ export default class RichText extends CustomElement {
onImgLoad: { type: Function },
render_styling: { type: Boolean },
show_images: { type: Boolean },
embed_videos: { type: Boolean },
show_me_message: { type: Boolean },
text: { type: String },
}
@ -21,22 +22,24 @@ export default class RichText extends CustomElement {
constructor () {
super();
this.offset = 0;
this.embed_audio = false;
this.embed_videos = false;
this.mentions = [];
this.offset = 0;
this.render_styling = false;
this.show_images = false;
this.embed_videos = false;
this.show_me_message = false;
}
render () {
const options = {
embed_audio: this.embed_audio,
embed_videos: this.embed_videos,
nick: this.nick,
onImgClick: this.onImgClick,
onImgLoad: this.onImgLoad,
render_styling: this.render_styling,
show_images: this.show_images,
embed_videos: this.embed_videos,
show_me_message: this.show_me_message,
}
return renderRichText(this.text, this.offset, this.mentions, options);

View File

@ -14,7 +14,7 @@ class StylingDirective extends Directive {
txt,
offset,
mentions,
Object.assign(options, { 'show_images': false, 'embed_videos': false })
Object.assign(options, { 'show_images': false, 'embed_videos': false, 'embed_audio': false })
);
return html`${until(transform(t), html`${t}`)}`;
}

View File

@ -1,7 +1,8 @@
import URI from 'urijs';
import log from '@converse/headless/log';
import tpl_audio from 'templates/audio.js';
import tpl_image from 'templates/image.js';
import tpl_video from '../templates/video.js';
import tpl_video from 'templates/video.js';
import { _converse, api } from '@converse/headless/core';
import { containsDirectives, getDirectiveAndLength, getDirectiveTemplate, isQuoteDirective } from './styling.js';
import {
@ -13,8 +14,11 @@ import {
import {
filterQueryParamsFromURL,
getHyperlinkTemplate,
isImageURL,
getURI,
isAudioDomainAllowed,
isAudioURL,
isImageDomainAllowed,
isImageURL,
isVideoDomainAllowed,
isVideoURL
} from 'utils/html';
@ -66,6 +70,8 @@ export class RichText extends String {
*/
constructor (text, offset = 0, mentions = [], options = {}) {
super(text);
this.embed_audio = options?.embed_audio;
this.embed_videos = options?.embed_videos;
this.mentions = mentions;
this.nick = options?.nick;
this.offset = offset;
@ -76,7 +82,6 @@ export class RichText extends String {
this.references = [];
this.render_styling = options?.render_styling;
this.show_images = options?.show_images;
this.embed_videos = options?.embed_videos;
}
/**
@ -114,6 +119,8 @@ export class RichText extends String {
});
} else if (this.embed_videos && isVideoURL(url_text) && isVideoDomainAllowed(url_text)) {
template = tpl_video({ 'url': filtered_url });
} else if (this.embed_audio && isAudioURL(url_text) && isAudioDomainAllowed(url_text)) {
template = tpl_audio(filtered_url);
} else {
template = getHyperlinkTemplate(filtered_url);
}

View File

@ -1,7 +1,5 @@
import { html } from "lit";
import { html } from 'lit';
export default (o) => html`
<audio controls src="${o.url}"></audio>
<a target="_blank" rel="noopener" href="${o.url}">${o.label_download}</a>
`;
export default (url) => {
return html`<audio controls src="${url}"></audio><a target="_blank" rel="noopener" href="${url}">${url}</a>`;
}

View File

@ -94,6 +94,20 @@ export function isImageURL (url) {
return regex?.test(url) || isURLWithImageExtension(url);
}
export function isAudioDomainAllowed (url) {
const embed_audio = api.settings.get('embed_audio');
if (!Array.isArray(embed_audio)) {
return embed_audio;
}
try {
const audio_domain = getURI(url).domain();
return embed_audio.includes(audio_domain);
} catch (error) {
log.debug(error);
return false;
}
}
export function isVideoDomainAllowed (url) {
const embed_videos = api.settings.get('embed_videos');
if (!Array.isArray(embed_videos)) {
@ -122,7 +136,7 @@ export function isImageDomainAllowed (url) {
}
}
function getFileName (uri) {
export function getFileName (uri) {
try {
return decodeURI(uri.filename());
} catch (error) {
@ -131,12 +145,8 @@ function getFileName (uri) {
}
}
function renderAudioURL (_converse, uri) {
const { __ } = _converse;
return tpl_audio({
'url': uri.toString(),
'label_download': __('Download audio file "%1$s"', getFileName(uri))
});
function renderAudioURL (url) {
return tpl_audio(url);
}
function renderImageURL (_converse, uri) {
@ -170,7 +180,7 @@ u.getOOBURLMarkup = function (_converse, url) {
if (u.isVideoURL(uri)) {
return tpl_video({ url });
} else if (u.isAudioURL(uri)) {
return renderAudioURL(_converse, uri);
return renderAudioURL(url);
} else if (u.isImageURL(uri)) {
return renderImageURL(_converse, uri);
} else {