diff --git a/src/equicordplugins/gifCollections/index.tsx b/src/equicordplugins/gifCollections/index.tsx index cf9592141..fad3c214e 100644 --- a/src/equicordplugins/gifCollections/index.tsx +++ b/src/equicordplugins/gifCollections/index.tsx @@ -22,7 +22,7 @@ import { Alerts, Button, ContextMenuApi, FluxDispatcher, Menu, React, showToast, import { addToCollection, cache_collections, createCollection, DATA_COLLECTION_NAME, deleteCollection, fixPrefix, getCollections, getGifById, getItemCollectionNameFromId, moveGifToCollection, refreshCacheCollection, removeFromCollection, renameCollection, updateGif } from "./utils/collectionManager"; import { getFormat } from "./utils/getFormat"; import { getGif } from "./utils/getGif"; -import { refreshGifUrl } from "./utils/refreshUrl"; +import { batchRefreshAttachmentUrls, isCdnUrlExpired } from "./utils/refreshUrl"; import { downloadCollections, uploadGifCollections } from "./utils/settingsUtils"; import { uuidv4 } from "./utils/uuidv4"; @@ -385,11 +385,44 @@ export default definePlugin({ width: g.width, height: g.height })).reverse(); + + const expiredGifs = collection.gifs.filter(g => g.src && g.url && (isCdnUrlExpired(g.src) || isCdnUrlExpired(g.url))); + if (expiredGifs.length > 0) { + const allUrls = [...new Set( + expiredGifs.flatMap(g => [g.src, g.url].filter((u): u is string => !!u && isCdnUrlExpired(u))) + )]; + const refreshInBatches = async () => { + const fullMap: Record = {}; + for (let i = 0; i < allUrls.length; i += 50) { + const batch = allUrls.slice(i, i + 50); + const result = await batchRefreshAttachmentUrls(batch); + Object.assign(fullMap, result); + } + return fullMap; + }; + refreshInBatches().then(async refreshMap => { + if (!Object.keys(refreshMap).length) return; + let anyUpdated = false; + for (const gif of expiredGifs) { + const newSrc = refreshMap[gif.src] ?? gif.src; + const newUrl = refreshMap[gif.url] ?? gif.url; + if (newSrc !== gif.src || newUrl !== gif.url) { + await updateGif(gif.id, { ...gif, src: newSrc, url: newUrl }); + anyUpdated = true; + } + } + if (!anyUpdated) return; + const { query } = instance.props; + FluxDispatcher.dispatch({ type: "GIF_PICKER_QUERY", query: `${query} ` }); + await new Promise(r => setTimeout(r, 2500)); + FluxDispatcher.dispatch({ type: "GIF_PICKER_QUERY", query }); + }); + } } } }, hidePrefix(name) { - return name.split(":".length > 1) ? name.replace(/.+?:/, "") : name; + return name.includes(":") ? name.replace(/.+?:/, "") : name; }, insertCollections(instance) { const shouldRemoveAll = settings.store.onlyShowCollections; @@ -547,40 +580,6 @@ const RemoveItemContextMenu = ({ type, nameOrId, instance }) => ( }} /> - { - const gifInfo = getGifById(nameOrId); - if (!gifInfo) return; - - if (!gifInfo.channelId || !gifInfo.messageId || !gifInfo.attachmentId) { - showToast("Cannot refresh: GIF missing metadata (re-add to collection)", Toasts.Type.FAILURE); - return; - } - - showToast("Refreshing URL...", Toasts.Type.MESSAGE); - const refreshedGif = await refreshGifUrl(gifInfo); - - if (refreshedGif) { - await updateGif(nameOrId, refreshedGif); - const collectionName = getItemCollectionNameFromId(nameOrId); - FluxDispatcher.dispatch({ - type: "GIF_PICKER_QUERY", - query: `${collectionName} ` - }); - FluxDispatcher.dispatch({ - type: "GIF_PICKER_QUERY", - query: `${collectionName}` - }); - showToast("URL refreshed successfully", Toasts.Type.SUCCESS); - } else { - showToast("Failed to refresh URL", Toasts.Type.FAILURE); - } - }} - /> - { +export function isCdnUrlExpired(url: string): boolean { try { - const response = await RestAPI.get({ - url: `/channels/${channelId}/messages`, - query: { - limit: 1, - around: messageId - } + const ex = new URL(url).searchParams.get("ex"); + if (!ex) return false; + return parseInt(ex, 16) * 1000 < Date.now(); + } catch { + return false; + } +} + +export async function batchRefreshAttachmentUrls(urls: string[]): Promise> { + try { + const response = await RestAPI.post({ + url: "/attachments/refresh-urls", + body: { attachment_urls: urls } }); - - if (response.ok && response.body.length > 0) { - return response.body.find((m: any) => m.id === messageId); + if (!response.ok) return {}; + const map: Record = {}; + for (const { original, refreshed } of response.body.refreshed_urls) { + map[original] = refreshed; } - } catch (error) { - console.error("Failed to fetch message for GIF refresh:", error); + return map; + } catch { + return {}; } - return null; -} - -export async function refreshGifUrl(gif: Gif): Promise { - if (!gif.channelId || !gif.messageId || !gif.attachmentId) { - return null; - } - - const message = await fetchMessageFromAPI(gif.channelId, gif.messageId); - if (!message) { - return null; - } - - const attachment = message.attachments?.find((a: any) => a.id === gif.attachmentId); - if (!attachment) { - return null; - } - - return { - ...gif, - src: attachment.proxy_url, - url: attachment.url - }; }