fix/gifCollections (#821)

* fix refreshing

* woah 50 limit

* fixes

* Increase delay in GIF picker query dispatch

---------

Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
Jake 2026-02-21 02:35:08 +00:00 • committed by GitHub
parent 1cf516f228
commit dad036fb98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 72 deletions

View file

@ -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<string>(
expiredGifs.flatMap(g => [g.src, g.url].filter((u): u is string => !!u && isCdnUrlExpired(u)))
)];
const refreshInBatches = async () => {
const fullMap: Record<string, string> = {};
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<void>(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 }) => (
}}
/>
<Menu.MenuSeparator />
<Menu.MenuItem
key="refresh-url"
id="refresh-url"
label="Refresh URL"
action={async () => {
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);
}
}}
/>
<Menu.MenuSeparator />
<Menu.MenuItem
key="copy-url"
id="copy-url"

View file

@ -4,46 +4,31 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Gif } from "@equicordplugins/gifCollections/types";
import { RestAPI } from "@webpack/common";
async function fetchMessageFromAPI(channelId: string, messageId: string): Promise<any> {
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<Record<string, string>> {
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<string, string> = {};
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<Gif | null> {
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
};
}