Pārlūkot izejas kodu

Eagerly fetch headers so that we can cache the result.

JC Brand 3 mēneši atpakaļ
vecāks
revīzija
f4dac965ac

+ 1 - 1
src/headless/plugins/chat/model.js

@@ -204,7 +204,7 @@ class ChatBox extends ModelWithVCard(ModelWithMessages(ModelWithContact(ColorAwa
             sender: 'me',
             time: (new Date()).toISOString(),
             type: this.get('message_type'),
-        }, u.getMediaURLsMetadata(text));
+        }, await u.getMediaURLsMetadata(text));
 
         /**
          * *Hook* which allows plugins to update the attributes of an outgoing message.

+ 2 - 1
src/headless/plugins/chat/parsers.js

@@ -168,5 +168,6 @@ export async function parseMessage (stanza) {
     // We call this after the hook, to allow plugins (like omemo) to decrypt encrypted
     // messages, since we need to parse the message text to determine whether
     // there are media urls.
-    return Object.assign(attrs, u.getMediaURLsMetadata(attrs.is_encrypted ? attrs.plaintext : attrs.body));
+    const metadata = await u.getMediaURLsMetadata(attrs.is_encrypted ? attrs.plaintext : attrs.body);
+    return Object.assign(attrs, metadata);
 }

+ 1 - 1
src/headless/plugins/muc/muc.js

@@ -1117,7 +1117,7 @@ class MUC extends ModelWithVCard(ModelWithMessages(ColorAwareModel(ChatBoxBase))
             sender: 'me',
             type: 'groupchat',
             original_text: text,
-        }, u.getMediaURLsMetadata(text));
+        }, await u.getMediaURLsMetadata(text));
 
         /**
          * *Hook* which allows plugins to update the attributes of an outgoing

+ 1 - 1
src/headless/plugins/muc/occupant.js

@@ -185,7 +185,7 @@ class MUCOccupant extends ModelWithVCard(ModelWithMessages(ColorAwareModel(Model
                 to: this.get("from") ?? `${muc.get("jid")}/${this.get("nick")}`,
                 type: "chat",
             },
-            u.getMediaURLsMetadata(text)
+            await u.getMediaURLsMetadata(text)
         );
 
         /**

+ 2 - 1
src/headless/plugins/muc/parsers.js

@@ -334,7 +334,8 @@ export async function parseMUCMessage(original_stanza, chatbox) {
     // We call this after the hook, to allow plugins to decrypt encrypted
     // messages, since we need to parse the message text to determine whether
     // there are media urls.
-    return Object.assign(attrs, u.getMediaURLsMetadata(attrs.is_encrypted ? attrs.plaintext : attrs.body));
+    const metadata = await u.getMediaURLsMetadata(attrs.is_encrypted ? attrs.plaintext : attrs.body);
+    return Object.assign(attrs, metadata);
 }
 
 /**

+ 7 - 6
src/headless/types/utils/index.d.ts

@@ -27,15 +27,16 @@ declare const _default: {
     checkFileTypes(types: string[], url: string | URL): boolean;
     isURLWithImageExtension(url: string | URL): boolean;
     isGIFURL(url: string | URL): boolean;
-    isAudioURL(url: string | URL): boolean;
-    isVideoURL(url: string | URL): boolean;
-    isImageURL(url: string | URL): boolean;
+    isAudioURL(url: string | URL, headers?: Headers): boolean;
+    isVideoURL(url: string | URL, headers?: Headers): boolean;
+    isImageURL(url: string | URL, headers?: Headers): boolean;
     isEncryptedFileURL(url: string | URL): boolean;
     withinString(string: string, callback: Function, options?: import("./types.js").ProcessStringOptions): string;
-    getMetadataForURL(o: import("./types.js").MediaURLIndexes): import("./types.js").MediaURLMetadata;
-    getMediaURLsMetadata(text: string, offset?: number): {
+    getHeaders(url: string): Promise<Headers>;
+    getMetadataForURL(o: import("./types.js").MediaURLIndexes): Promise<import("./types.js").MediaURLMetadata>;
+    getMediaURLsMetadata(text: string, offset?: number): Promise<{
         media_urls?: import("./types.js").MediaURLMetadata[];
-    };
+    }>;
     getMediaURLs(arr: Array<import("./types.js").MediaURLMetadata>, text: string): import("./types.js").MediaURLMetadata[];
     addMediaURLsOffset(arr: Array<import("./types.js").MediaURLMetadata>, text: string, offset?: number): import("./types.js").MediaURLMetadata[];
     firstCharToUpperCase(text: string): string;

+ 0 - 1
src/headless/types/utils/types.d.ts

@@ -21,6 +21,5 @@ export type MediaURLMetadata = MediaURLIndexes & {
     is_gif?: boolean;
     is_image?: boolean;
     is_video?: boolean;
-    content_type?: string;
 };
 //# sourceMappingURL=types.d.ts.map

+ 16 - 8
src/headless/types/utils/url.d.ts

@@ -30,17 +30,20 @@ export function isURLWithImageExtension(url: string | URL): boolean;
 export function isGIFURL(url: string | URL): boolean;
 /**
  * @param {string|URL} url
+ * @param {Headers} [headers]
  */
-export function isAudioURL(url: string | URL): boolean;
+export function isAudioURL(url: string | URL, headers?: Headers): boolean;
 /**
  * @param {string|URL} url
+ * @param {Headers} [headers]
  */
-export function isVideoURL(url: string | URL): boolean;
+export function isVideoURL(url: string | URL, headers?: Headers): boolean;
 /**
  * @param {string|URL} url
+ * @param {Headers} [headers]
  * @returns {boolean}
  */
-export function isImageURL(url: string | URL): boolean;
+export function isImageURL(url: string | URL, headers?: Headers): boolean;
 /**
  * @param {string|URL} url
  */
@@ -58,19 +61,24 @@ export function isEncryptedFileURL(url: string | URL): boolean;
  * @returns {string} The modified string after processing all matches.
  */
 export function withinString(string: string, callback: Function, options?: import("./types").ProcessStringOptions): string;
+/**
+ * @param {string} url
+ * @returns {Promise<Headers>}
+ */
+export function getHeaders(url: string): Promise<Headers>;
 /**
  * @param {import("./types").MediaURLIndexes} o
- * @returns {import("./types").MediaURLMetadata}
+ * @returns {Promise<import("./types").MediaURLMetadata>}
  */
-export function getMetadataForURL(o: import("./types").MediaURLIndexes): import("./types").MediaURLMetadata;
+export function getMetadataForURL(o: import("./types").MediaURLIndexes): Promise<import("./types").MediaURLMetadata>;
 /**
  * @param {string} text
  * @param {number} offset
- * @returns {{media_urls?: import("./types").MediaURLMetadata[]}}
+ * @returns {Promise<{media_urls?: import("./types").MediaURLMetadata[]}>}
  */
-export function getMediaURLsMetadata(text: string, offset?: number): {
+export function getMediaURLsMetadata(text: string, offset?: number): Promise<{
     media_urls?: import("./types").MediaURLMetadata[];
-};
+}>;
 /**
  * @param {Array<import("./types").MediaURLMetadata>} arr
  * @param {string} text

+ 39 - 11
src/headless/utils/url.js

@@ -78,23 +78,35 @@ export function isGIFURL(url) {
 
 /**
  * @param {string|URL} url
+ * @param {Headers} [headers]
  */
-export function isAudioURL(url) {
+export function isAudioURL(url, headers) {
+    if (headers?.get("content-type")?.startsWith("audio")) {
+        return true;
+    }
     return checkFileTypes([".ogg", ".mp3", ".m4a"], url);
 }
 
 /**
  * @param {string|URL} url
+ * @param {Headers} [headers]
  */
-export function isVideoURL(url) {
+export function isVideoURL(url, headers) {
+    if (headers?.get("content-type")?.startsWith("video")) {
+        return true;
+    }
     return checkFileTypes([".mp4", ".webm"], url);
 }
 
 /**
  * @param {string|URL} url
+ * @param {Headers} [headers]
  * @returns {boolean}
  */
-export function isImageURL(url) {
+export function isImageURL(url, headers) {
+    if (headers?.get("content-type")?.startsWith("video")) {
+        return true;
+    }
     const regex = settings.get("image_urls_regex");
     return regex?.test(url) || isURLWithImageExtension(url);
 }
@@ -174,17 +186,33 @@ export function withinString(string, callback, options) {
     return string;
 }
 
+/**
+ * @param {string} url
+ * @returns {Promise<Headers>}
+ */
+export async function getHeaders(url) {
+    try {
+        const response = await fetch(url, { method: "HEAD" });
+        return response.headers;
+    } catch (e) {
+        console.debug(`Error calling HEAD on url ${url}: ${e}`);
+        return null;
+    }
+}
+
 /**
  * @param {import("./types").MediaURLIndexes} o
- * @returns {import("./types").MediaURLMetadata}
+ * @returns {Promise<import("./types").MediaURLMetadata>}
  */
-export function getMetadataForURL(o) {
+export async function getMetadataForURL(o) {
+    const fetch_headers = settings_api.get("fetch_url_headers");
+    const headers = fetch_headers ? await getHeaders(o.url) : null;
     return {
         ...o,
         is_gif: isGIFURL(o.url),
-        is_audio: isAudioURL(o.url),
-        is_image: isImageURL(o.url),
-        is_video: isVideoURL(o.url),
+        is_audio: isAudioURL(o.url, headers),
+        is_image: isImageURL(o.url, headers),
+        is_video: isVideoURL(o.url, headers),
         is_encrypted: isEncryptedFileURL(o.url),
     };
 }
@@ -192,9 +220,9 @@ export function getMetadataForURL(o) {
 /**
  * @param {string} text
  * @param {number} offset
- * @returns {{media_urls?: import("./types").MediaURLMetadata[]}}
+ * @returns {Promise<{media_urls?: import("./types").MediaURLMetadata[]}>}
  */
-export function getMediaURLsMetadata(text, offset = 0) {
+export async function getMediaURLsMetadata(text, offset = 0) {
     const objs = [];
     if (!text) {
         return {};
@@ -228,7 +256,7 @@ export function getMediaURLsMetadata(text, offset = 0) {
         log.debug(error);
     }
 
-    const media_urls = objs.map((o) => getMetadataForURL(o));
+    const media_urls = await Promise.all(objs.map(getMetadataForURL));
     return media_urls.length ? { media_urls } : {};
 }
 

+ 6 - 16
src/shared/texture/texture.js

@@ -14,7 +14,6 @@ import {
     collapseLineBreaks,
     containsDirectives,
     getDirectiveAndLength,
-    getHeaders,
     isQuoteDirective,
     isSpotifyTrack,
     isString,
@@ -23,13 +22,8 @@ import {
 } from "./utils.js";
 import { styling_map } from "./constants.js";
 
-const {
-    addMediaURLsOffset,
-    convertASCII2Emoji,
-    getCodePointReferences,
-    getMediaURLsMetadata,
-    getShortnameReferences,
-} = u;
+const { addMediaURLsOffset, convertASCII2Emoji, getCodePointReferences, getMediaURLsMetadata, getShortnameReferences } =
+    u;
 
 /**
  * @class Texture
@@ -147,13 +141,6 @@ export class Texture extends String {
         } else if (api.settings.get("embed_3rd_party_media_players") && isSpotifyTrack(url)) {
             const song_id = url.split("/track/")[1];
             template = tplSpotify(song_id, url, this.hide_media_urls);
-        } else {
-            if (this.shouldRenderMedia(url, "audio") && api.settings.get("fetch_url_headers")) {
-                const headers = await getHeaders(url);
-                if (headers?.get("content-type")?.startsWith("audio")) {
-                    template = tplAudio(filtered_url, this.hide_media_urls, headers.get("Icy-Name"));
-                }
-            }
         }
         return template || getHyperlinkTemplate(filtered_url);
     }
@@ -167,7 +154,10 @@ export class Texture extends String {
      */
     async addHyperlinks(text, local_offset) {
         const full_offset = local_offset + this.offset;
-        const urls_meta = this.media_urls || getMediaURLsMetadata(text, local_offset).media_urls || [];
+        const urls_meta =
+            this.media_urls ||
+            (await getMediaURLsMetadata(text, local_offset)).media_urls ||
+            [];
         const media_urls = addMediaURLsOffset(urls_meta, text, full_offset);
         await Promise.all(
             media_urls

+ 14 - 12
src/types/utils/index.d.ts

@@ -11,15 +11,16 @@ declare const _default: {
     checkFileTypes(types: string[], url: string | URL): boolean;
     isURLWithImageExtension(url: string | URL): boolean;
     isGIFURL(url: string | URL): boolean;
-    isAudioURL(url: string | URL): boolean;
-    isVideoURL(url: string | URL): boolean;
-    isImageURL(url: string | URL): boolean;
+    isAudioURL(url: string | URL, headers?: Headers): boolean;
+    isVideoURL(url: string | URL, headers?: Headers): boolean;
+    isImageURL(url: string | URL, headers?: Headers): boolean;
     isEncryptedFileURL(url: string | URL): boolean;
     withinString(string: string, callback: Function, options?: import("headless/types/utils/types.js").ProcessStringOptions): string;
-    getMetadataForURL(o: import("headless/types/utils/types.js").MediaURLIndexes): import("headless/types/utils/types.js").MediaURLMetadata;
-    getMediaURLsMetadata(text: string, offset?: number): {
+    getHeaders(url: string): Promise<Headers>;
+    getMetadataForURL(o: import("headless/types/utils/types.js").MediaURLIndexes): Promise<import("headless/types/utils/types.js").MediaURLMetadata>;
+    getMediaURLsMetadata(text: string, offset?: number): Promise<{
         media_urls?: import("headless/types/utils/types.js").MediaURLMetadata[];
-    };
+    }>;
     getMediaURLs(arr: Array<import("headless/types/utils/types.js").MediaURLMetadata>, text: string): import("headless/types/utils/types.js").MediaURLMetadata[];
     addMediaURLsOffset(arr: Array<import("headless/types/utils/types.js").MediaURLMetadata>, text: string, offset?: number): import("headless/types/utils/types.js").MediaURLMetadata[];
     firstCharToUpperCase(text: string): string;
@@ -119,15 +120,16 @@ declare const _default: {
         checkFileTypes(types: string[], url: string | URL): boolean;
         isURLWithImageExtension(url: string | URL): boolean;
         isGIFURL(url: string | URL): boolean;
-        isAudioURL(url: string | URL): boolean;
-        isVideoURL(url: string | URL): boolean;
-        isImageURL(url: string | URL): boolean;
+        isAudioURL(url: string | URL, headers?: Headers): boolean;
+        isVideoURL(url: string | URL, headers?: Headers): boolean;
+        isImageURL(url: string | URL, headers?: Headers): boolean;
         isEncryptedFileURL(url: string | URL): boolean;
         withinString(string: string, callback: Function, options?: import("headless/types/utils/types.js").ProcessStringOptions): string;
-        getMetadataForURL(o: import("headless/types/utils/types.js").MediaURLIndexes): import("headless/types/utils/types.js").MediaURLMetadata;
-        getMediaURLsMetadata(text: string, offset?: number): {
+        getHeaders(url: string): Promise<Headers>;
+        getMetadataForURL(o: import("headless/types/utils/types.js").MediaURLIndexes): Promise<import("headless/types/utils/types.js").MediaURLMetadata>;
+        getMediaURLsMetadata(text: string, offset?: number): Promise<{
             media_urls?: import("headless/types/utils/types.js").MediaURLMetadata[];
-        };
+        }>;
         getMediaURLs(arr: Array<import("headless/types/utils/types.js").MediaURLMetadata>, text: string): import("headless/types/utils/types.js").MediaURLMetadata[];
         addMediaURLsOffset(arr: Array<import("headless/types/utils/types.js").MediaURLMetadata>, text: string, offset?: number): import("headless/types/utils/types.js").MediaURLMetadata[];
         firstCharToUpperCase(text: string): string;