This commit is contained in:
thororen1234 2024-06-01 15:34:40 -04:00
parent 93a96bc120
commit d18e1b2415
43 changed files with 6 additions and 9034 deletions

View file

@ -1,111 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { DataStore } from "@api/index";
import { Toasts } from "@webpack/common";
import { settings } from "./index";
import { Collection, Gif } from "./types";
import { getFormat } from "./utils/getFormat";
export const DATA_COLLECTION_NAME = "gif-collections-collections";
// this is here bec async + react class component dont play nice and stutters happen. IF theres a better way of doing it pls let me know
export let cache_collections: Collection[] = [];
export const getCollections = async (): Promise<Collection[]> => (await DataStore.get<Collection[]>(DATA_COLLECTION_NAME)) ?? [];
export const getCollection = async (name: string): Promise<Collection | undefined> => {
const collections = await getCollections();
return collections.find(c => c.name === name);
};
export const getCachedCollection = (name: string): Collection | undefined => cache_collections.find(c => c.name === name);
export const createCollection = async (name: string, gifs: Gif[]): Promise<void> => {
const collections = await getCollections();
const duplicateCollection = collections.find(c => c.name === `gc:${name}`);
if (duplicateCollection)
return Toasts.show({
message: "That collection already exists",
type: Toasts.Type.FAILURE,
id: Toasts.genId(),
options: {
duration: 3000,
position: Toasts.Position.BOTTOM
}
});
// gifs shouldnt be empty because to create a collection you need to right click an image / gif and then create it yk. but cant hurt to have a null-conditional check RIGHT?
const latestGifSrc = gifs[gifs.length - 1]?.src ?? settings.store.defaultEmptyCollectionImage;
const collection = {
name: `gc:${name}`,
src: latestGifSrc,
format: getFormat(latestGifSrc),
type: "Category",
gifs
};
await DataStore.set(DATA_COLLECTION_NAME, [...collections, collection]);
return await refreshCacheCollection();
};
export const addToCollection = async (name: string, gif: Gif): Promise<void> => {
const collections = await getCollections();
const collectionIndex = collections.findIndex(c => c.name === name);
if (collectionIndex === -1) return console.warn("collection not found");
collections[collectionIndex].gifs.push(gif);
collections[collectionIndex].src = gif.src;
collections[collectionIndex].format = getFormat(gif.src);
await DataStore.set(DATA_COLLECTION_NAME, collections);
return await refreshCacheCollection();
};
export const removeFromCollection = async (id: string): Promise<void> => {
const collections = await getCollections();
const collectionIndex = collections.findIndex(c => c.gifs.some(g => g.id === id));
if (collectionIndex === -1) return console.warn("collection not found");
// Remove The Gif
collections[collectionIndex].gifs = collections[collectionIndex].gifs.filter(g => g.id !== id);
const collection = collections[collectionIndex];
const latestGifSrc = collection.gifs.length ? collection.gifs[collection.gifs.length - 1].src : settings.store.defaultEmptyCollectionImage;
collections[collectionIndex].src = latestGifSrc;
collections[collectionIndex].format = getFormat(latestGifSrc);
await DataStore.set(DATA_COLLECTION_NAME, collections);
return await refreshCacheCollection();
};
export const deleteCollection = async (name: string): Promise<void> => {
const collections = await getCollections();
const col = collections.filter(c => c.name !== name);
await DataStore.set(DATA_COLLECTION_NAME, col);
await refreshCacheCollection();
};
export const refreshCacheCollection = async (): Promise<void> => {
cache_collections = await getCollections();
};

View file

@ -1,21 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// cant change them now eh. My friend uses this plugin. LOVE YOU FREEZER
export const GIF_ITEM_PREFIX = "gc-moment:";
export const GIF_COLLECTION_PREFIX = "gc:";

View file

@ -1,352 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Plugin idea by brainfreeze (668137937333911553) 😎
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Alerts, Button, ContextMenuApi, FluxDispatcher, Forms, Menu, React, TextInput, useCallback, useState } from "@webpack/common";
import * as CollectionManager from "./CollectionManager";
import { GIF_COLLECTION_PREFIX, GIF_ITEM_PREFIX } from "./constants";
import { Category, Collection, Gif, Props } from "./types";
import { getFormat } from "./utils/getFormat";
import { getGif } from "./utils/getGif";
import { downloadCollections, uploadGifCollections } from "./utils/settingsUtils";
import { uuidv4 } from "./utils/uuidv4";
export const settings = definePluginSettings({
defaultEmptyCollectionImage: {
description: "The image / gif that will be shown when a collection has no images / gifs",
type: OptionType.STRING,
default: "https://i.imgur.com/TFatP8r.png"
},
importGifs: {
type: OptionType.COMPONENT,
description: "Import Collections",
component: () =>
<Button onClick={async () =>
// if they have collections show the warning
(await CollectionManager.getCollections()).length ? Alerts.show({
title: "Are you sure?",
body: "Importing collections will overwrite your current collections.",
confirmText: "Import",
// wow this works?
confirmColor: Button.Colors.RED,
cancelText: "Nevermind",
onConfirm: async () => uploadGifCollections()
}) : uploadGifCollections()}>
Import Collections
</Button>,
},
exportGifs: {
type: OptionType.COMPONENT,
description: "Export Collections",
component: () =>
<Button onClick={downloadCollections}>
Export Collections
</Button>
}
});
const addCollectionContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
if (!props) return;
const { message, itemSrc, itemHref, target } = props;
const gif = getGif(message, itemSrc ?? itemHref, target);
if (!gif) return;
const group = findGroupChildrenByChildId("open-native-link", children) ?? findGroupChildrenByChildId("copy-link", children);
if (group && !group.some(child => child?.props?.id === "add-to-collection")) {
group.push(
// if i do it the normal way i get a invalid context menu thingy error -> Menu API only allows Items and groups of Items as children.
MenuThingy({ gif })
);
}
};
export default definePlugin({
name: "Gif Collection",
// need better description eh
description: "Allows you to have collections of gifs",
authors: [Devs.Aria],
patches: [
{
find: "renderCategoryExtras",
replacement: [
// This patch adds the collections to the gif part yk
{
match: /(\i\.render=function\(\){)(.{1,50}getItemGrid)/,
replace: "$1;$self.insertCollections(this);$2"
},
// Hides the gc: from the name gc:monkeh -> monkeh
{
match: /(\i\.renderCategoryExtras=function\((?<props>\i)\){)var (?<varName>\i)=\i\.name,/,
replace: "$1var $<varName>=$self.hidePrefix($<props>),"
},
// Replaces this.props.resultItems with the collection.gifs
{
match: /(\i\.renderContent=function\(\){)(.{1,50}resultItems)/,
replace: "$1;$self.renderContent(this);$2"
},
// Delete context menu for collection
{
match: /(\i\.render=function\(\){.{1,100}renderExtras.{1,200}onClick:this\.handleClick,)/,
replace: "$1onContextMenu: (e) => $self.collectionContextMenu(e, this),"
},
]
},
/*
problem:
when you click your collection in the gifs picker discord enters the collection name into the search bar
which causes discord to fetch the gifs from their api. This causes a tiny flash when the gifs have fetched successfully
solution:
if query starts with gc: and collection is not null then return early and prevent the fetch
*/
{
find: "type:\"GIF_PICKER_QUERY\"",
replacement: {
match: /(function \i\((?<query>\i),\i\){.{1,200}dispatch\({type:"GIF_PICKER_QUERY".{1,20};)/,
replace:
"$&if($self.shouldStopFetch($<query>)) return;"
}
},
],
settings,
start() {
CollectionManager.refreshCacheCollection();
addContextMenuPatch("message", addCollectionContextMenuPatch);
},
stop() {
removeContextMenuPatch("message", addCollectionContextMenuPatch);
},
CollectionManager,
oldTrendingCat: null as Category[] | null,
sillyInstance: null as any,
sillyContentInstance: null as any,
get collections(): Collection[] {
CollectionManager.refreshCacheCollection();
return CollectionManager.cache_collections;
},
renderContent(instance) {
if (instance.props.query.startsWith(GIF_COLLECTION_PREFIX)) {
this.sillyContentInstance = instance;
const collection = this.collections.find(c => c.name === instance.props.query);
if (!collection) return;
instance.props.resultItems = collection.gifs.map(g => ({
id: g.id,
format: getFormat(g.src),
src: g.src,
url: g.url,
width: g.width,
height: g.height
})).reverse();
}
},
hidePrefix(props: Category) {
return props.name.split(":").length > 1 ? props.name.replace(/.+?:/, "") : props.name;
},
insertCollections(instance: { props: Props; }) {
try {
this.sillyInstance = instance;
if (instance.props.trendingCategories.length && instance.props.trendingCategories[0].type === "Trending")
this.oldTrendingCat = instance.props.trendingCategories;
if (this.oldTrendingCat != null)
instance.props.trendingCategories = this.collections.reverse().concat(this.oldTrendingCat as Collection[]);
} catch (err) {
console.error(err);
}
},
shouldStopFetch(query: string) {
if (query.startsWith(GIF_COLLECTION_PREFIX)) {
const collection = this.collections.find(c => c.name === query);
if (collection != null) return true;
}
return false;
},
collectionContextMenu(e: React.UIEvent, instance) {
const { item } = instance.props;
if (item?.name?.startsWith(GIF_COLLECTION_PREFIX))
return ContextMenuApi.openContextMenu(e, () =>
<RemoveItemContextMenu
type="collection"
onConfirm={() => { this.sillyInstance && this.sillyInstance.forceUpdate(); }}
nameOrId={instance.props.item.name} />
);
if (item?.id?.startsWith(GIF_ITEM_PREFIX))
return ContextMenuApi.openContextMenu(e, () =>
<RemoveItemContextMenu
type="gif"
onConfirm={() => { this.sillyContentInstance && this.sillyContentInstance.forceUpdate(); }}
nameOrId={instance.props.item.id}
/>);
const { src, url, height, width } = item;
if (src && url && height != null && width != null && !item.id?.startsWith(GIF_ITEM_PREFIX))
return ContextMenuApi.openContextMenu(e, () =>
<Menu.Menu
navId="gif-collection-id"
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
aria-label="Gif Collections"
>
{/* if i do it the normal way i get a invalid context menu thingy error -> Menu API only allows Items and groups of Items as children.*/}
{MenuThingy({ gif: { ...item, id: uuidv4() } })}
</Menu.Menu>
);
return null;
},
});
// stolen from spotify controls
const RemoveItemContextMenu = ({ type, nameOrId, onConfirm }: { type: "gif" | "collection", nameOrId: string, onConfirm: () => void; }) => (
<Menu.Menu
navId="gif-collection-id"
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
aria-label={type === "collection" ? "Delete Collection" : "Remove"}
>
<Menu.MenuItem
key="delete-collection"
id="delete-collection"
label={type === "collection" ? "Delete Collection" : "Remove"}
action={() =>
// Stolen from Review components
type === "collection" ? Alerts.show({
title: "Are you sure?",
body: "Do you really want to delete this collection?",
confirmText: "Delete",
confirmColor: Button.Colors.RED,
cancelText: "Nevermind",
onConfirm: async () => {
await CollectionManager.deleteCollection(nameOrId);
onConfirm();
}
}) : CollectionManager.removeFromCollection(nameOrId).then(() => onConfirm())}
>
</Menu.MenuItem>
</Menu.Menu>
);
const MenuThingy: React.FC<{ gif: Gif; }> = ({ gif }) => {
CollectionManager.refreshCacheCollection();
const collections = CollectionManager.cache_collections;
return (
<Menu.MenuItem
label="Add To Collection"
key="add-to-collection"
id="add-to-collection"
>
{collections.map(col => (
<Menu.MenuItem
key={col.name}
id={col.name}
label={col.name.replace(/.+?:/, "")}
action={() => CollectionManager.addToCollection(col.name, gif)}
/>
))}
<Menu.MenuSeparator />
<Menu.MenuItem
key="create-collection"
id="create-collection"
label="Create Collection"
action={() => {
openModal(modalProps => (
<CreateCollectionModal onClose={modalProps.onClose} gif={gif} modalProps={modalProps} />
));
}}
/>
</Menu.MenuItem>
);
};
interface CreateCollectionModalProps {
gif: Gif;
onClose: () => void,
modalProps: ModalProps;
}
function CreateCollectionModal({ gif, onClose, modalProps }: CreateCollectionModalProps) {
const [name, setName] = useState("");
const onSubmit = useCallback((e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
if (!name.length) return;
CollectionManager.createCollection(name, [gif]);
onClose();
}, [name]);
return (
<ModalRoot {...modalProps}>
<form onSubmit={onSubmit}>
<ModalHeader>
<Forms.FormText>Create Collection</Forms.FormText>
</ModalHeader>
<ModalContent>
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Collection Name</Forms.FormTitle>
<TextInput onChange={(e: string) => setName(e)} />
</ModalContent>
<div style={{ marginTop: "1rem" }}>
<ModalFooter>
<Button
type="submit"
color={Button.Colors.GREEN}
disabled={!name.length}
onClick={onSubmit}
>
Create
</Button>
</ModalFooter>
</div>
</form>
</ModalRoot>
);
}

View file

@ -1,44 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export enum Format { NONE = 0, IMAGE = 1, VIDEO = 2 }
export interface Category {
type: "Trending" | "Category";
name: string;
src: string;
format: Format;
gifs?: Gif[];
}
export interface Gif {
id: string,
src: string;
url: string;
height: number,
width: number;
}
export interface Props {
favorites: { [src: string]: any; };
trendingCategories: Category[];
}
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
export type Collection = WithRequired<Category, "gifs">;

View file

@ -1,23 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const cleanUrl = (url: string) => {
const urlObject = new URL(url);
urlObject.search = "";
return urlObject.href;
};

View file

@ -1,27 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Format } from "../types";
import { getUrlExtension } from "./getUrlExtension";
const videoExtensions = ["mp4", "ogg", "webm", "avi", "wmv", "flv", "mov", "mkv", "m4v"];
export function getFormat(url: string) {
const extension = getUrlExtension(url);
return url.startsWith("https://media.tenor") || extension == null || videoExtensions.includes(extension) ? Format.VIDEO : Format.IMAGE;
}

View file

@ -1,123 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { MessageStore, SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general";
import { Gif } from "../types";
import { cleanUrl } from "./cleanUrl";
import { isAudio } from "./isAudio";
import { uuidv4 } from "./uuidv4";
export function getGifByTarget(url: string, target?: HTMLDivElement | null): Gif | null {
const liElement = target?.closest("li");
if (!target || !liElement || !liElement.id) return null;
const [channelId, messageId] = liElement.id.split("-").slice(2);
// the isValidSnowFlake part may not be nessesery cuse either way (valid or not) message will be undefined if it doenst find a message /shrug
if (!channelId || !messageId || !isValidSnowFlake(channelId) || !isValidSnowFlake(messageId)) return null;
const message = MessageStore.getMessage(channelId, messageId);
if (!message || !message.embeds.length && !message.attachments.length) return null;
return getGifByMessageAndUrl(url, message);
}
export function getGifByMessageAndTarget(target: HTMLDivElement, message: Message) {
const url = target.closest('[class^="imageWrapper"]')?.querySelector("video")?.src ?? target.closest('[class^="imageWrapper"]')?.querySelector("img")?.src;
if (!url) return null;
return getGifByMessageAndUrl(url, message);
}
export function getGifByMessageAndUrl(url: string, message: Message): Gif | null {
if (!message.embeds.length && !message.attachments.length || isAudio(url))
return null;
url = cleanUrl(url);
// find embed with matching url or image/thumbnail url
const embed = message.embeds.find(e => e.url === url || e.image?.url === url || e.image?.proxyURL === url || e.video?.proxyURL === url || e.thumbnail?.proxyURL === url); // no point in checking thumbnail/video url because no way of getting it eh. discord renders the img/video element with proxy urls
if (embed) {
if (embed.image)
return {
id: uuidv4(),
height: embed.image.height,
width: embed.image.width,
src: embed.image.proxyURL,
url: embed.image.url,
};
// Tennor
if (embed.video && embed.video.proxyURL) return {
id: uuidv4(),
height: embed.video.height,
width: embed.video.width,
src: embed.video.proxyURL,
url: embed.provider?.name === "Tenor" ? embed.url ?? embed.video.url : embed.video.url,
};
// Youtube thumbnails and other stuff idk
if (embed.thumbnail && embed.thumbnail.proxyURL) return {
id: uuidv4(),
height: embed.thumbnail.height,
width: embed.thumbnail.width,
src: embed.thumbnail.proxyURL,
url: embed.thumbnail.url,
};
}
const attachment = message.attachments.find(a => a.url === url || a.proxy_url === url);
if (attachment) return {
id: uuidv4(),
height: attachment.height ?? 50,
width: attachment.width ?? 50,
src: attachment.proxy_url,
url: attachment.url
};
return null;
}
export const getGif = (message: Message | null, url: string | null, target: HTMLDivElement | null) => {
if (message && url) {
const gif = getGifByMessageAndUrl(url, message);
if (!gif) return null;
return gif;
}
if (message && target && !url) {
const gif = getGifByMessageAndTarget(target, message);
if (!gif) return null;
return gif;
}
if (url && target && !message) {
// youtube thumbnail url is message link for some reason eh
const gif = getGifByTarget(url.startsWith("https://discord.com/") ? target.parentElement?.querySelector("img")?.src ?? url : url, target);
if (!gif) return null;
return gif;
}
};
function isValidSnowFlake(snowflake: string) {
return !Number.isNaN(SnowflakeUtils.extractTimestamp(snowflake));
}

View file

@ -1,23 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export function getUrlExtension(url: string) {
// tennor stuff is like //media.tenor/blah/blah
if (!url.startsWith("https:")) url = "https:" + url;
return new URL(url).pathname.split(".").pop();
}

View file

@ -1,27 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getUrlExtension } from "./getUrlExtension";
const audioExtensions = ["mp3", "wav", "ogg", "aac", "m4a", "wma", "flac"];
export function isAudio(url: string) {
const extension = getUrlExtension(url);
return extension && audioExtensions.includes(extension);
}

View file

@ -1,131 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { DataStore } from "@api/index";
import { Toasts } from "@webpack/common";
import { DATA_COLLECTION_NAME, getCollections, refreshCacheCollection } from "../CollectionManager";
// 99% of this is coppied from src\utils\settingsSync.ts
export async function downloadCollections() {
const filename = "gif-collections.json";
const exportData = await exportCollections();
const data = new TextEncoder().encode(exportData);
if (IS_WEB) {
const file = new File([data], filename, { type: "application/json" });
const a = document.createElement("a");
a.href = URL.createObjectURL(file);
a.download = filename;
document.body.appendChild(a);
a.click();
setImmediate(() => {
URL.revokeObjectURL(a.href);
document.body.removeChild(a);
});
} else {
DiscordNative.fileManager.saveWithDialog(data, filename);
}
}
export async function exportCollections() {
const collections = await getCollections();
return JSON.stringify({ collections }, null, 4);
}
export async function importCollections(data: string) {
try {
var parsed = JSON.parse(data);
} catch (err) {
console.log(data);
throw new Error("Failed to parse JSON: " + String(err));
}
if ("collections" in parsed) {
await DataStore.set(DATA_COLLECTION_NAME, parsed.collections);
await refreshCacheCollection();
} else
throw new Error("Invalid Collections");
}
export async function uploadGifCollections(showToast = true): Promise<void> {
if (IS_WEB) {
const input = document.createElement("input");
input.type = "file";
input.style.display = "none";
input.accept = "application/json";
input.onchange = async () => {
const file = input.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
try {
await importCollections(reader.result as string);
if (showToast) toastSuccess();
} catch (err) {
console.error(err);
// new Logger("SettingsSync").error(err);
if (showToast) toastFailure(err);
}
};
reader.readAsText(file);
};
document.body.appendChild(input);
input.click();
setImmediate(() => document.body.removeChild(input));
} else {
const [file] = await DiscordNative.fileManager.openFiles({
filters: [
{ name: "Gif Collections", extensions: ["json"] },
{ name: "all", extensions: ["*"] }
]
});
if (file) {
try {
await importCollections(new TextDecoder().decode(file.data));
if (showToast) toastSuccess();
} catch (err) {
console.error(err);
// new Logger("SettingsSync").error(err);
if (showToast) toastFailure(err);
}
}
}
}
const toastSuccess = () => Toasts.show({
type: Toasts.Type.SUCCESS,
message: "Settings successfully imported.",
id: Toasts.genId()
});
const toastFailure = (err: any) => Toasts.show({
type: Toasts.Type.FAILURE,
message: `Failed to import settings: ${String(err)}`,
id: Toasts.genId()
});

View file

@ -1,29 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { GIF_ITEM_PREFIX } from "../constants";
export function uuidv4() {
let d = new Date().getTime();
d += performance.now();
return `${GIF_ITEM_PREFIX}xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, c => {
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
});
}