diff --git a/.gitattributes b/.gitattributes index 6313b56c578..e410a5314ef 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ * text=auto eol=lf +README.md merge=ours diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ed7d5ca71b..54ca4178dfb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,8 @@ on: - scripts/build/** - package.json - pnpm-lock.yaml + workflow_dispatch: + env: FORCE_COLOR: true @@ -50,31 +52,9 @@ jobs: echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - name: Upload DevBuild as release - if: github.repository == 'Vendicated/Vencord' run: | gh release upload devbuild --clobber dist/* gh release edit devbuild --title "DevBuild $RELEASE_TAG" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ env.release_tag }} - - - name: Upload DevBuild to builds repo - if: github.repository == 'Vendicated/Vencord' - run: | - git config --global user.name "$USERNAME" - git config --global user.email actions@github.com - - git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload - cd upload - - GLOBIGNORE=.git:.gitignore:README.md:LICENSE - rm -rf * - cp -r ../dist/* . - - git add -A - git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" - git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git - env: - API_TOKEN: ${{ secrets.BUILDS_TOKEN }} - GH_REPO: Vencord/builds - USERNAME: GitHub-Actions diff --git a/.github/workflows/sync_fork.yml b/.github/workflows/sync_fork.yml new file mode 100644 index 00000000000..671ba619fdd --- /dev/null +++ b/.github/workflows/sync_fork.yml @@ -0,0 +1,20 @@ +name: Sync Fork + +on: + schedule: + - cron: '*/30 * * * *' # every 30 minutes + workflow_dispatch: # on button click + +jobs: + sync: + + runs-on: ubuntu-latest + + steps: + - uses: tgymnich/fork-sync@v1.8 + with: + owner: StupidityDB + token: ${{ secrets.PERSONAL_TOKEN }} + ignore_fail: true + base: main + head: main diff --git a/.gitignore b/.gitignore index 135673a6d73..12c91843d92 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ lerna-debug.log* .pnpm-debug.log* *.tsbuildinfo -src/userplugins - ExtensionCache/ settings/ + +src/userplugins diff --git a/README.md b/README.md index a43c9f83477..ecaaaa46564 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ -# Vencord +# VencordPlus -[![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Vee/cord&color=2185D0&logo=data:image/png;base64,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)](https://codeberg.org/Vee/cord) +Fork of client mod Vencord with lots of extra plugins including ReviewDB -The cutest Discord client mod - -| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) | -|:--:| -| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) | +![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) ## Features @@ -24,29 +20,7 @@ The cutest Discord client mod ## Installing / Uninstalling -Visit https://vencord.dev/download - -## Join our Support/Community Server - -https://discord.gg/D9uwnFnqmd - -## Sponsors - -| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** | -|:--:| -| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) | -| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* | - - -## Star History - - - - - - Star History Chart - - +[![Download and run the Installer](https://img.shields.io/github/v/release/StupidityDB/VencordPlusInstaller?label=Download%20Vencord%2B%20Installer&style=for-the-badge)](https://github.com/StupidityDB/VencordPlusInstaller#vencord-installer) ## Disclaimer diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 5c34ad03873..2f0d2bff75d 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -81,7 +81,7 @@ export const globPlugins = kind => ({ }); build.onLoad({ filter, namespace: "import-plugins" }, async () => { - const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"]; + const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins", "plusplugins"]; let code = ""; let plugins = "\n"; let i = 0; diff --git a/src/plusplugins/Timezones/Utils.ts b/src/plusplugins/Timezones/Utils.ts new file mode 100644 index 00000000000..a7cf495b68d --- /dev/null +++ b/src/plusplugins/Timezones/Utils.ts @@ -0,0 +1,122 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import * as DataStore from "@api/DataStore"; +import { findStoreLazy } from "@webpack"; +export const DATASTORE_KEY = "plugins.Timezones.savedTimezones"; + +import { debounce } from "@shared/debounce"; +import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; + +import { CustomTimezonePreference } from "./settings"; + +export interface TimezoneDB { + [userId: string]: string; +} + +export const API_URL = "https://timezonedb.catvibers.me"; +const Cache: Record = {}; + +const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore"); + +export function getTimeString(timezone: string, timestamp = new Date()): string { + try { + const locale = UserSettingsProtoStore.settings.localization.locale.value; + return new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "numeric", timeZone: timezone }).format(timestamp); // we hate javascript + } catch (e) { + return "Error"; // incase it gets invalid timezone from api, probably not gonna happen but if it does this will prevent discord from crashing + } +} + + +// A map of ids and callbacks that should be triggered on fetch +const requestQueue: Record void)[]> = {}; + + +async function bulkFetchTimezones(ids: string[]): Promise { + try { + const req = await fetch(`${API_URL}/api/user/bulk`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-User-Agent": VENCORD_USER_AGENT + }, + body: JSON.stringify(ids), + }); + + return await req.json() + .then((res: { [userId: string]: { timezoneId: string; } | null; }) => { + const tzs = (Object.keys(res).map(userId => { + return res[userId] && { [userId]: res[userId]!.timezoneId }; + }).filter(Boolean) as TimezoneDB[]).reduce((acc, cur) => ({ ...acc, ...cur }), {}); + + Object.assign(Cache, tzs); + return tzs; + }); + } catch (e) { + console.error("Timezone fetching failed: ", e); + } +} + + +// Executes all queued requests and calls their callbacks +const bulkFetch = debounce(async () => { + const ids = Object.keys(requestQueue); + const timezones = await bulkFetchTimezones(ids); + if (!timezones) { + // retry after 15 seconds + setTimeout(bulkFetch, 15000); + return; + } + + for (const id of ids) { + // Call all callbacks for the id + requestQueue[id].forEach(c => c(timezones[id])); + delete requestQueue[id]; + } +}); + +export function getUserTimezone(discordID: string, strategy: CustomTimezonePreference): + Promise { + + return new Promise(res => { + const timezone = (DataStore.get(DATASTORE_KEY) as Promise).then(tzs => tzs?.[discordID]); + timezone.then(tz => { + if (strategy === CustomTimezonePreference.Always) { + if (tz) res(tz); + else res(undefined); + return; + } + + if (tz && strategy === CustomTimezonePreference.Secondary) + res(tz); + else { + if (discordID in Cache) res(Cache[discordID]); + else if (discordID in requestQueue) requestQueue[discordID].push(res); + // If not already added, then add it and call the debounced function to make sure the request gets executed + else { + requestQueue[discordID] = [res]; + bulkFetch(); + } + } + }); + }); +} + +const gist = "e321f856f98676505efb90aad82feff1"; +const revision = "91034ee32eff93a7cb62d10702f6b1d01e0309e6"; +const timezonesLink = `https://gist.githubusercontent.com/ArjixWasTaken/${gist}/raw/${revision}/timezones.json`; + +export const getAllTimezones = async (): Promise => { + if (typeof Intl !== "undefined" && "DateTimeFormat" in Intl) { + try { + const timeZoneOptions = Intl.DateTimeFormat().resolvedOptions(); + return [timeZoneOptions.timeZone]; + } catch { } + } + + return await fetch(timezonesLink).then(tzs => tzs.json()); +}; diff --git a/src/plusplugins/Timezones/index.tsx b/src/plusplugins/Timezones/index.tsx new file mode 100644 index 00000000000..59c42e7371e --- /dev/null +++ b/src/plusplugins/Timezones/index.tsx @@ -0,0 +1,243 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import * as DataStore from "@api/DataStore"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { React, SearchableSelect, Text, Toasts, UserStore } from "@webpack/common"; +import { Message, User } from "discord-types/general"; + +import settings from "./settings"; +const classNames = findByPropsLazy("customStatusSection"); + + +import { CogWheel, DeleteIcon } from "@components/Icons"; +import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; +import { makeLazy } from "@utils/lazy"; +import { classes } from "@utils/misc"; +import { useForceUpdater } from "@utils/react"; + +import { API_URL, DATASTORE_KEY, getAllTimezones, getTimeString, getUserTimezone, TimezoneDB } from "./Utils"; +const styles = findByPropsLazy("timestampInline"); + +const useTimezones = makeLazy(getAllTimezones); + +export default definePlugin({ + settings, + + name: "Timezones", + description: "Allows you to see and set the timezones of other users.", + authors: [Devs.mantikafasi, Devs.Arjix], + + commands: [ + { + name: "timezone", + description: "Sends link to a website that shows timezone string, useful if you want to know your friends timezone", + execute: () => { + return { content: "https://gh.lewisakura.moe/timezone/" }; + } + } + ], + + settingsAboutComponent: () => { + const href = `${API_URL}?client_mod=${encodeURIComponent(VENCORD_USER_AGENT)}`; + return ( + + A plugin that displays the local time for specific users using their timezone.
+ Timezones can either be set manually or fetched automatically from the TimezoneDB +
+ ); + }, + + patches: [ + { + find: "copyMetaData:\"User Tag\"", + replacement: { + + match: /return(\(0,.\.jsx\)\(.\.default,{className:.+?}\)]}\)}\))/, + replace: "return [$1, $self.getProfileTimezonesComponent(arguments[0])]" + }, + }, + { + // thank you https://github.com/Syncxv/vc-timezones/blob/master/index.tsx for saving me from painful work + find: ".badgesContainer,", + replacement: { + match: /id:\(0,\i\.getMessageTimestampId\)\(\i\),timestamp.{1,50}}\),/, + replace: "$&,$self.getTimezonesComponent(arguments[0])," + } + } + ], + + getProfileTimezonesComponent: ({ user }: { user: User; }) => { + const { preference, showTimezonesInProfile } = settings.use(["preference", "showTimezonesInProfile"]); + + const [timezone, setTimezone] = React.useState(); + const [isInEditMode, setIsInEditMode] = React.useState(false); + const [timezones, setTimezones] = React.useState([]); + + const forceUpdate = useForceUpdater(); + + React.useEffect(() => { + useTimezones().then(setTimezones); + getUserTimezone(user.id, preference).then(tz => setTimezone(tz)); + + // Rerender every 10 seconds to stay in sync. + const interval = setInterval(forceUpdate, 10 * 1000); + + return () => clearInterval(interval); + }, [preference]); + + if (!showTimezonesInProfile) + return null; + + return ( + + {!isInEditMode && + { + if (timezone) { + Toasts.show({ + type: Toasts.Type.MESSAGE, + message: timezone, + id: Toasts.genId() + }); + } + }} + > + {(timezone) ? getTimeString(timezone) : "No timezone set"} + + } + + {isInEditMode && ( + + ({ label: tz, value: tz }))} + value={timezone ? { label: timezone, value: timezone } : undefined} + onChange={value => { setTimezone(value); }} + /> + + )} + + + { + if (!isInEditMode) { + setIsInEditMode(true); + return; + } + + if (!timezone) { + setIsInEditMode(false); + return; + } + + DataStore.update(DATASTORE_KEY, (oldValue: TimezoneDB | undefined) => { + oldValue = oldValue || {}; + oldValue[user.id] = timezone; + return oldValue; + }).then(() => { + Toasts.show({ + type: Toasts.Type.SUCCESS, + message: "Timezone set!", + id: Toasts.genId() + }); + + setIsInEditMode(false); + }).catch(err => { + console.error(err); + Toasts.show({ + type: Toasts.Type.FAILURE, + message: "Something went wrong, please try again later.", + id: Toasts.genId() + }); + }); + }} + color="var(--primary-330)" + height="16" + width="16" + /> + + {isInEditMode && + { + DataStore.update(DATASTORE_KEY, (oldValue: TimezoneDB | undefined) => { + oldValue = oldValue || {}; + delete oldValue[user.id]; + return oldValue; + }).then(async () => { + Toasts.show({ + type: Toasts.Type.SUCCESS, + message: "Timezone removed!", + id: Toasts.genId() + }); + setIsInEditMode(false); + setTimezone(await getUserTimezone(user.id, preference)); + }).catch(err => { + console.error(err); + Toasts.show({ + type: Toasts.Type.FAILURE, + message: "Something went wrong, please try again later.", + id: Toasts.genId() + }); + }); + }} + color="var(--red-360)" + height="16" + width="16" + /> + } + + + ); + }, + + getTimezonesComponent: ({ message }: { message: Message; }) => { + + const { showTimezonesInChat, preference } = settings.use(["preference", "showTimezonesInChat"]); + const [timezone, setTimezone] = React.useState(); + + React.useEffect(() => { + if (!showTimezonesInChat) return; + + getUserTimezone(message.author.id, preference).then(tz => setTimezone(tz)); + }, [showTimezonesInChat, preference]); + + if (!showTimezonesInChat || message.author.id === UserStore.getCurrentUser()?.id) + return null; + + return ( + + {timezone && "• " + getTimeString(timezone, message.timestamp.toDate())} + ); + } +}); diff --git a/src/plusplugins/Timezones/settings.tsx b/src/plusplugins/Timezones/settings.tsx new file mode 100644 index 00000000000..357dddde193 --- /dev/null +++ b/src/plusplugins/Timezones/settings.tsx @@ -0,0 +1,59 @@ +/* + * 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 . +*/ + +import { definePluginSettings } from "@api/Settings"; +import { OptionType } from "@utils/types"; + +export enum CustomTimezonePreference { + Never, + Secondary, + Always +} + +export default definePluginSettings({ + preference: { + type: OptionType.SELECT, + description: "When to use custom timezones over TimezoneDB.", + options: [ + { + label: "Never use custom timezones.", + value: CustomTimezonePreference.Never, + }, + { + label: "Prefer custom timezones over TimezoneDB", + value: CustomTimezonePreference.Secondary, + default: true, + }, + { + label: "Always use custom timezones.", + value: CustomTimezonePreference.Always, + }, + ], + default: CustomTimezonePreference.Secondary, + }, + showTimezonesInChat: { + type: OptionType.BOOLEAN, + description: "Show timezones in chat", + default: true, + }, + showTimezonesInProfile: { + type: OptionType.BOOLEAN, + description: "Show timezones in profile", + default: true, + }, +}); diff --git a/src/plusplugins/dndbypass/index.tsx b/src/plusplugins/dndbypass/index.tsx new file mode 100644 index 00000000000..f0602a1b24f --- /dev/null +++ b/src/plusplugins/dndbypass/index.tsx @@ -0,0 +1,77 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { DataStore } from "@api/index"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { findStoreLazy } from "@webpack"; +import { Menu, showToast } from "@webpack/common"; +import { User } from "discord-types/general"; + +import { settings } from "./settings"; + +export let userWhitelist: string[] = []; + +export const DATASTORE_KEY = "DnDBypass_whitelistedUsers"; +const SelfPresenceStore = findStoreLazy("SelfPresenceStore"); + +const userContextMenuPatch: NavContextMenuPatchCallback = (children, props: { user: User, onClose(): void; }) => { + children.push( + , + whitelistUser(props.user)} + /> + ); +}; + +function whitelistUser(user: User) { + if (userWhitelist.includes(user.id)) { + userWhitelist = userWhitelist.filter(id => id !== user.id); + showToast("Removed user from DND whitelist"); + } else { + userWhitelist.push(user.id); + showToast("Added user to DND whitelist"); + } + + DataStore.set(DATASTORE_KEY, userWhitelist); +} + +export default definePlugin({ + name: "DnDBypass", + description: "Bypass DND for specified users", + authors: [Devs.mantikafasi], + + patches: [ + { + find: "ThreadMemberFlags.NO_MESSAGES&&", + replacement: { + match: /return!\(null!=.+?&&!0/, + replace: "if (!n.guild_id && $self.shouldNotify(t)) {return true;} $&" + } + } + ], + settings, + contextMenus: { "user-context": userContextMenuPatch }, + + shouldNotify(author: User) { + console.log(author); + if (SelfPresenceStore.getStatus() !== "dnd") { + return false; + } + return userWhitelist.includes(author.id); + }, + + async start() { + userWhitelist = await DataStore.get(DATASTORE_KEY) ?? []; + }, + + stop() { } + +}); diff --git a/src/plusplugins/dndbypass/settings.tsx b/src/plusplugins/dndbypass/settings.tsx new file mode 100644 index 00000000000..b63a2162acd --- /dev/null +++ b/src/plusplugins/dndbypass/settings.tsx @@ -0,0 +1,102 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { definePluginSettings } from "@api/Settings"; +import { Flex } from "@components/Flex"; +import { DeleteIcon } from "@components/Icons"; +import { useForceUpdater } from "@utils/react"; +import { OptionType } from "@utils/types"; +import { Button, Forms, React, TextInput, useState } from "@webpack/common"; + +import { DATASTORE_KEY, userWhitelist } from "."; + +export const settings = definePluginSettings({ + whitelistedUsers: { + type: OptionType.COMPONENT, + component: () => { + const update = useForceUpdater(); + return (); + }, + description: "Users that will bypass DND mode", + } + +}); + + +function Input({ initialValue, onChange, placeholder }: { + placeholder: string; + initialValue: string; + onChange(value: string): void; +}) { + const [value, setValue] = useState(initialValue); + return ( + value !== initialValue && onChange(value)} + /> + ); +} + +function WhitelistedUsersComponent(props: { update: () => void; }) { + const { update } = props; + + async function onClickRemove(index: number) { + userWhitelist.splice(index, 1); + + await DataStore.set(DATASTORE_KEY, userWhitelist); + update(); + } + + async function onChange(e: string, index: number) { + if (index === userWhitelist.length - 1) + userWhitelist.push(""); + + userWhitelist[index] = e; + + if (index !== userWhitelist.length - 1) + userWhitelist.splice(index, 1); + + await DataStore.set(DATASTORE_KEY, userWhitelist); + update(); + } + + return ( + <> + Users to get whitelisted + + { + userWhitelist.map((user, index) => + + + + onChange(e, index)} + /> + + + + + ) + } + + + ); +} diff --git a/src/plusplugins/favouriteImage/index.ts b/src/plusplugins/favouriteImage/index.ts new file mode 100644 index 00000000000..2767ddb88cc --- /dev/null +++ b/src/plusplugins/favouriteImage/index.ts @@ -0,0 +1,27 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +declare global { + interface RegExpConstructor { + _test?: (str: string) => boolean; + } +} + +export default definePlugin({ + name: "FavouriteImage", + description: "Allows you to favourite an image.", + authors: [Devs.VeygaX, Devs.Davri], + start() { + RegExp._test ??= RegExp.prototype.test; + RegExp.prototype.test = function (str) { return (RegExp._test ?? (() => false)).call(this.source === "\\.gif($|\\?|#)" ? /\.(gif|png|jpe?g|webp)($|\?|#)/i : this, str); }; + }, + stop() { + RegExp.prototype.test = RegExp._test ?? (() => false); + delete RegExp._test; + }, +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index cce276ef851..1ea3e2ee3da 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -450,6 +450,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Oleh Polisan", id: 242305263313485825n }, + VeygaX: { + name: "VeygaX", + id: 1119938236245094521n + }, + Davri: { + name: "Davri", + id: 457579346282938368n + }, GabiRP: { name: "GabiRP", id: 507955112027750401n