Merge branch 'dev'
This commit is contained in:
commit
8f19b84966
162 changed files with 3167 additions and 166 deletions
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
|
|
@ -69,13 +69,6 @@ jobs:
|
|||
run: |
|
||||
gh release upload latest --clobber dist/release/*
|
||||
|
||||
- name: Create Tag
|
||||
uses: butlerlogic/action-autotag@1.1.2
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
with:
|
||||
tag_prefix: "v"
|
||||
|
||||
- name: Upload Plugin JSONs to Equibored repo
|
||||
run: |
|
||||
git config --global user.name "GitHub-Actions"
|
||||
|
|
|
|||
97
.github/workflows/publish.yml
vendored
97
.github/workflows/publish.yml
vendored
|
|
@ -1,44 +1,75 @@
|
|||
name: Release Browser Extension
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: check that tag matches package.json version
|
||||
run: |
|
||||
pkg_version="v$(jq -r .version < package.json)"
|
||||
if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then
|
||||
echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Get package.json version
|
||||
id: pkg
|
||||
run: echo "version=v$(jq -r .version < package.json)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
- name: Get latest Git tag
|
||||
id: tag
|
||||
run: |
|
||||
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||
echo "latest=$latest_tag" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
- name: Check if package.json version is newer
|
||||
id: check
|
||||
run: |
|
||||
pkg="${{ steps.pkg.outputs.version }}"
|
||||
latest="${{ steps.tag.outputs.latest }}"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
echo "Latest tag: $latest"
|
||||
echo "Package version: $pkg"
|
||||
|
||||
- name: Build web
|
||||
run: pnpm buildWebStandalone
|
||||
if [ "$pkg" != "$latest" ] && [ "$(sort -V <<<"$latest"$'\n'"$pkg" | tail -n1)" = "$pkg" ]; then
|
||||
echo "release=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "release=false" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish extension
|
||||
run: |
|
||||
cd dist/browser/chromium-unpacked
|
||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
|
||||
env:
|
||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
- name: Stop if not newer
|
||||
if: steps.check.outputs.release == 'false'
|
||||
run: |
|
||||
echo "No new version to release (package.json ${{ steps.pkg.outputs.version }} <= latest tag ${{ steps.tag.outputs.latest }})"
|
||||
exit 0
|
||||
|
||||
- name: Create Tag
|
||||
uses: butlerlogic/action-autotag@1.1.2
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
with:
|
||||
tag_prefix: "v"
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build web
|
||||
run: pnpm buildWebStandalone
|
||||
|
||||
- name: Publish extension
|
||||
run: |
|
||||
cd dist/browser/chromium-unpacked
|
||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
|
||||
env:
|
||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
|
|
|
|||
12
src/equicordplugins/allCallTimers/README.md
Normal file
12
src/equicordplugins/allCallTimers/README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# AllCallTimers
|
||||
|
||||
Adds a timer to each user in a guild voice call.
|
||||
|
||||
Original author: [AllCallTimersDiscordPlugin](https://github.com/Max-Herbold/AllCallTimersDiscordPlugin)
|
||||
|
||||
<img width="323rem" alt="image" src="https://cdn.nest.rip/uploads/43f2c839-59ee-4d49-9e28-6eff0edca79f.png">
|
||||
<img width="300rem" alt="image" src="https://cdn.nest.rip/uploads/3598369c-302a-464b-b8dd-e2314e2d4fb5.png">
|
||||
<br/>
|
||||
<img height="346rem" alt="image" src="https://cdn.nest.rip/uploads/a080902e-4acb-4782-869a-8fc66a631ba0.png">
|
||||
<img width="424rem" alt="image" src="https://cdn.nest.rip/uploads/43bec8a6-ee35-433c-8dfa-0e4c8ac48661.png">
|
||||
<br/>
|
||||
|
|
@ -119,7 +119,7 @@ export default definePlugin({
|
|||
predicate: () => settings.store.showWithoutHover,
|
||||
replacement: [
|
||||
{
|
||||
match: /function\(\)\{.+:""(?=.*?userId:(\i))/,
|
||||
match: /function \i\(\)\{.+:""(?=.*?userId:(\i))/,
|
||||
replace: "$&,$self.renderTimer($1.id),"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
3
src/equicordplugins/altKrispSwitch/README.md
Normal file
3
src/equicordplugins/altKrispSwitch/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# AltKrispSwitch
|
||||
|
||||
Swaps between None and Krisp instead of Krisp and Standard
|
||||
3
src/equicordplugins/alwaysExpandProfiles/README.md
Normal file
3
src/equicordplugins/alwaysExpandProfiles/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# AlwaysExpandProfiles
|
||||
|
||||
Always expands a user or bots profile skipping the popout
|
||||
|
|
@ -17,18 +17,12 @@ export default definePlugin({
|
|||
authors: [EquicordDevs.thororen],
|
||||
patches: [
|
||||
{
|
||||
find: '"UserProfilePopout"})',
|
||||
find: '"view-profile"',
|
||||
replacement: {
|
||||
match: /(?<=user:(\i).*?"PRESS_VIEW_PROFILE".{0,150})return/,
|
||||
replace: "return $self.openUserProfile($1);"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '"BotUserProfilePopout"',
|
||||
replacement: {
|
||||
match: /(?<=user:(\i).*?"PRESS_VIEW_PROFILE".{0,100})return/,
|
||||
replace: "return $self.openUserProfile($1);"
|
||||
}
|
||||
match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200})return/,
|
||||
replace: "$1return $self.openUserProfile($2);"
|
||||
},
|
||||
all: true
|
||||
},
|
||||
],
|
||||
openUserProfile(user: User) {
|
||||
|
|
|
|||
3
src/equicordplugins/amITyping/README.md
Normal file
3
src/equicordplugins/amITyping/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# AmITyping
|
||||
|
||||
Simply shows if you are typing. This is almost exactly what it will look like for other users, so it may show after you have finished typing.
|
||||
9
src/equicordplugins/anammox/README.md
Normal file
9
src/equicordplugins/anammox/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Anammox
|
||||
|
||||
Removes Nitro-exclusive stuff from various places:
|
||||
|
||||
- The shops above private chats
|
||||
- The gift button in the chat box
|
||||
- The "billing" section in settings
|
||||
|
||||
While this can be done with css, a plugin is able to do it much more thorougly — for example, the shops are still accessible via keyboard navigation.
|
||||
10
src/equicordplugins/atSomeone/README.md
Normal file
10
src/equicordplugins/atSomeone/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# AtSomeone
|
||||
|
||||
Mention someone randomly
|
||||
|
||||
Due to some limitations, it won't show on autocomplete in dms/groups but it still works.
|
||||
|
||||
Do `@role*` to mention a random person with that role.
|
||||
|
||||

|
||||

|
||||
3
src/equicordplugins/autoJump/README.md
Normal file
3
src/equicordplugins/autoJump/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# AutoJump
|
||||
|
||||
Jumps to the first message in new messages for a channel
|
||||
5
src/equicordplugins/bannersEverywhere/README.md
Normal file
5
src/equicordplugins/bannersEverywhere/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# BannersEverywhere
|
||||
|
||||
Displays nitro and USRBG banners as a background in the member list
|
||||
|
||||

|
||||
7
src/equicordplugins/betterActivities/README.md
Normal file
7
src/equicordplugins/betterActivities/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# BetterActivities
|
||||
|
||||
Shows activity icons in the member list and allows showing all activities
|
||||
|
||||

|
||||

|
||||

|
||||
3
src/equicordplugins/betterAudioPlayer/README.md
Normal file
3
src/equicordplugins/betterAudioPlayer/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# BetterAudioPlayer
|
||||
|
||||
Adds visualizers to audio attachments
|
||||
7
src/equicordplugins/betterBanReasons/README.md
Normal file
7
src/equicordplugins/betterBanReasons/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# BetterBanReasons
|
||||
|
||||
* Create custom reasons to use in the Discord ban modal.
|
||||
* Show a text input by default instead of the options
|
||||
|
||||
<img width="593" alt="Settings screenshot" src="https://cdn.nest.rip/uploads/68a4c4ee-7406-4df0-9afd-f239f9344a10.png" />
|
||||
<img width="421" alt="Ban modal Screenshot" src="https://cdn.nest.rip/uploads/cea1aee3-c2ee-4ac7-86d5-dbbb2b774e80.png" />
|
||||
3
src/equicordplugins/betterBlockedUsers/README.md
Normal file
3
src/equicordplugins/betterBlockedUsers/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# BetterBlockedUsers
|
||||
|
||||
Adds searching and opening the profile of a blocked user
|
||||
|
|
@ -31,7 +31,7 @@ export default definePlugin({
|
|||
replace: "$self.renderUser($1,$2)",
|
||||
},
|
||||
{
|
||||
match: /(?<=\}=(\i).{0,10}(\i).useState\(.{0,1}\);)/,
|
||||
match: /(?<=\}=(\i).{0,10}(\i).useState\(\i\).{0,25}\};)/,
|
||||
replace: "let [searchResults,setSearchResults]=$2.useState([]);$self.setUpdateFunc($1,setSearchResults);"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
3
src/equicordplugins/betterCommands/README.md
Normal file
3
src/equicordplugins/betterCommands/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# BetterCommands
|
||||
|
||||
Adds/Fixes various things about commands
|
||||
11
src/equicordplugins/betterInvites/README.md
Normal file
11
src/equicordplugins/betterInvites/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# BetterInvites
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
# features
|
||||
|
||||
- be able to see the inviter and inviter profile
|
||||
- preview discoverable servers by clicking the server name
|
||||
- be able to see the invite relative expiration date
|
||||
3
src/equicordplugins/betterPlusReacts/README.md
Normal file
3
src/equicordplugins/betterPlusReacts/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# BetterPlusReacts
|
||||
|
||||
<https://cdn.nest.rip/uploads/1e1c130c-6b10-4582-982d-2cff379924dc.mp4>
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// https://github.com/Masterjoona/vc-betterplusreacts/blob/main/index.ts
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { MessageStore } from "@webpack/common";
|
||||
5
src/equicordplugins/blockKrisp/README.md
Normal file
5
src/equicordplugins/blockKrisp/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# BlockKrisp
|
||||
|
||||
Prevent Krisp from loading
|
||||
|
||||

|
||||
3
src/equicordplugins/bypassPinPrompt/README.md
Normal file
3
src/equicordplugins/bypassPinPrompt/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# BypassPinPrompt
|
||||
|
||||
Bypasses the prompt for pinning messages
|
||||
3
src/equicordplugins/bypassStatus/README.md
Normal file
3
src/equicordplugins/bypassStatus/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# BypassStatus
|
||||
|
||||
Bypass Status allows you to set specific guilds/channels to still receive notifications and calls from when in a set status.
|
||||
3
src/equicordplugins/channelBadges/README.md
Normal file
3
src/equicordplugins/channelBadges/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ChannelBadges
|
||||
|
||||
Badges for channels based on their type such as text, voice, forum, etc.
|
||||
7
src/equicordplugins/channelTabs/README.md
Normal file
7
src/equicordplugins/channelTabs/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# ChannelTabs
|
||||
|
||||
Group your commonly visited channels in tabs, like a browser
|
||||
|
||||

|
||||

|
||||

|
||||
3
src/equicordplugins/characterCounter/README.md
Normal file
3
src/equicordplugins/characterCounter/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CharacterCounter
|
||||
|
||||
Character counter for chat input
|
||||
3
src/equicordplugins/cleanChannelName/README.md
Normal file
3
src/equicordplugins/cleanChannelName/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CleanChannelName
|
||||
|
||||
Cleans emojis and other things out of channel names
|
||||
3
src/equicordplugins/clientSideBlock/README.md
Normal file
3
src/equicordplugins/clientSideBlock/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ClientSideBlock
|
||||
|
||||
Blocks a user client side so they don't know they're blocked
|
||||
|
|
@ -36,14 +36,6 @@ const settings = definePluginSettings(
|
|||
restartNeeded: true,
|
||||
default: true
|
||||
},
|
||||
/*
|
||||
hideNewUsers: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Should content from users with the \"I'm new here, say hi!\" badge be blocked\"",
|
||||
restartNeeded: true,
|
||||
default: true
|
||||
},
|
||||
*/
|
||||
blockedReplyDisplay: {
|
||||
type: OptionType.SELECT,
|
||||
description: "What should display instead of the message when someone replies to someone you have hidden",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# ClipsEnhancements
|
||||
|
||||
Add more Clip FPS and duration options, plus RPC tagging!
|
||||
3
src/equicordplugins/commandPalette/README.md
Normal file
3
src/equicordplugins/commandPalette/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CommandPalette
|
||||
|
||||
Navigating with a keyboard
|
||||
49
src/equicordplugins/commandPalette/doc.md
Normal file
49
src/equicordplugins/commandPalette/doc.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Command Palette Actions Guide
|
||||
|
||||
Welcome to the Command Palette Actions Guide! This guide serves to inform you on how to implement your own actions to the CommandPalette plugin. To do so you have two options: hardcoding or utilizing the `registerAction` function. This guide will focus on using the function as its best practice and is what should be used in most situations.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> While both methods hardcoding and using `registerAction` offer similar implementations, it's recommended to refrain from Hardcoding unless you solely plan on using this locally.
|
||||
|
||||
### Implementing Multiple Choice Modals
|
||||
|
||||
```ts
|
||||
registerAction({
|
||||
id: 'multipleChoiceCommand',
|
||||
label: 'Multiple Choice',
|
||||
callback: async () => {
|
||||
// Open a modal with multiple choices
|
||||
const choice = await openMultipleChoice([
|
||||
{ id: 'test1', label: 'Test 1' },
|
||||
{ id: 'test2', label: 'Test 2' },
|
||||
]);
|
||||
|
||||
// Log the selected choice with its label and ID
|
||||
console.log(`Selected ${choice.label} with the ID ${choice.id}`);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- **ID**: A unique identifier for the command/action, ensuring uniqueness across specific options in that modal instance.
|
||||
- **Label**: The text displayed in the command palette for user recognition.
|
||||
- **Callback (optional)**: The function executed when the command is triggered.
|
||||
|
||||
Inside the callback, the `openMultipleChoice` function opens a modal with a list of choices. Users can select an option, and upon choosing, a `ButtonAction` type is returned. The user's choice is then logged for reference.
|
||||
|
||||
### Implementing String Input Modals
|
||||
|
||||
```ts
|
||||
registerAction({
|
||||
id: 'stringInputCommand',
|
||||
label: 'String Input',
|
||||
callback: async () => {
|
||||
// Open a modal with a text input
|
||||
const text = await openSimpleTextInput();
|
||||
|
||||
// Log the inputted text to console
|
||||
console.log(`They typed: ${text}`);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
When the `stringInputCommand` is triggered, a modal with a simple text input field appears. Users can input text, and the entered string is returned. In this case, we log their input to console.
|
||||
3
src/equicordplugins/contentWarning/README.md
Normal file
3
src/equicordplugins/contentWarning/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ContentWarning
|
||||
|
||||
Specify certain words to blur
|
||||
|
|
@ -133,7 +133,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "ContentWarning",
|
||||
authors: [EquicordDevs.camila314],
|
||||
description: "Allows you to specify certain trigger words that will be blurred by default. Clicking on the blurred content will reveal it.",
|
||||
description: "Allows you to specify certain trigger words that will be blurred by default. Hovering on the blurred content will reveal it.",
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
3
src/equicordplugins/copyProfileColors/README.md
Normal file
3
src/equicordplugins/copyProfileColors/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CopyProfileColors
|
||||
|
||||
Allows copying profile colors
|
||||
3
src/equicordplugins/copyStatusUrls/README.md
Normal file
3
src/equicordplugins/copyStatusUrls/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CopyStatusUrls
|
||||
|
||||
Allows copying status urls
|
||||
3
src/equicordplugins/copyUserMention/README.md
Normal file
3
src/equicordplugins/copyUserMention/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CopyUserMention
|
||||
|
||||
Allows copying a users mention
|
||||
11
src/equicordplugins/customFolderIcons/README.md
Normal file
11
src/equicordplugins/customFolderIcons/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# CustomFolderIcons
|
||||
|
||||
Allows you to set custom images/svgs as folder icons
|
||||
|
||||
Available as "Change Icon" in the folder context menu
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
3
src/equicordplugins/customSounds/README.md
Normal file
3
src/equicordplugins/customSounds/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CustomSounds
|
||||
|
||||
Allows customizing discords sounds
|
||||
3
src/equicordplugins/customTimestamps/README.md
Normal file
3
src/equicordplugins/customTimestamps/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CustomTimestamps
|
||||
|
||||
Allows custom timestamp formatting
|
||||
3
src/equicordplugins/customUserColors/README.md
Normal file
3
src/equicordplugins/customUserColors/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CustomUserColors
|
||||
|
||||
Like IrcColors except allows setting custom user colors
|
||||
3
src/equicordplugins/cuteAnimeBoys/README.md
Normal file
3
src/equicordplugins/cuteAnimeBoys/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CuteAnimeBoys
|
||||
|
||||
Adds the cuteanimeboys command
|
||||
3
src/equicordplugins/cuteNekos/README.md
Normal file
3
src/equicordplugins/cuteNekos/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CuteNekos
|
||||
|
||||
Adds the nekos command
|
||||
3
src/equicordplugins/cutePats/README.md
Normal file
3
src/equicordplugins/cutePats/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# CutePats
|
||||
|
||||
Adds the pat command
|
||||
3
src/equicordplugins/demonstration/README.md
Normal file
3
src/equicordplugins/demonstration/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Demonstration
|
||||
|
||||
Helps for taking theme screenshots
|
||||
3
src/equicordplugins/disableCameras/README.md
Normal file
3
src/equicordplugins/disableCameras/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# DisableCameras
|
||||
|
||||
Disables cameras by default when in a voice call
|
||||
3
src/equicordplugins/discordDevBanner/README.md
Normal file
3
src/equicordplugins/discordDevBanner/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# DiscordDevBanner
|
||||
|
||||
Enables developer banner and adds custom options to display in it
|
||||
3
src/equicordplugins/dontFilterMe/README.md
Normal file
3
src/equicordplugins/dontFilterMe/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# DontFilterMe
|
||||
|
||||
Warns you if automod might flag your message
|
||||
13
src/equicordplugins/downloadify/README.md
Normal file
13
src/equicordplugins/downloadify/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Downloadify
|
||||
|
||||
Download various assets directly in Discord without having to open a browser or dig through HTML.
|
||||
|
||||
## Features
|
||||
1. Download non-image files such as ZIP files directly inside of Discord.
|
||||
- Allow or disallow unicode characters in file names.
|
||||
- Various image assets are available in multiple formats as Discord makes converted copies.
|
||||
- APNG and AWEBP are presented with the `.apng` and `.awebp` extensions during download in order to differentiate them from static PNG and WEBP variants, but upon download their extensions are converted back to `.png` and `.webp` respectively.
|
||||
2. Show a notification when downloads start and finish with a customizable duration.
|
||||
3. Add a download button to voice messages.
|
||||
4. Set a default directory or pick each time.
|
||||
- Pick whether to overwrite existing files or not.
|
||||
115
src/equicordplugins/downloadify/index.tsx
Normal file
115
src/equicordplugins/downloadify/index.tsx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { GDMContextMenu, GuildContextMenu, handleExpandedModalDownloadButtonClicked, handleHoverDownloadButtonClicked, MessageContextMenu, UserContextMenu, VoiceMessageDownloadButton } from "./utils/handlers";
|
||||
|
||||
export default definePlugin({
|
||||
name: "Downloadify",
|
||||
description: "Download various assets directly in Discord without having to open a browser or dig through HTML.",
|
||||
authors: [EquicordDevs.Etorix],
|
||||
hidden: IS_WEB,
|
||||
settings,
|
||||
|
||||
VoiceMessageDownloadButton,
|
||||
handleHoverDownloadButtonClicked,
|
||||
handleExpandedModalDownloadButtonClicked,
|
||||
|
||||
contextMenus: {
|
||||
"message": MessageContextMenu,
|
||||
"guild-context": GuildContextMenu,
|
||||
"user-context": UserContextMenu,
|
||||
"gdm-context": GDMContextMenu
|
||||
},
|
||||
|
||||
patches: [
|
||||
{
|
||||
// Pass the guild ID to the profile modal context. Used by the next patch.
|
||||
find: "clanTagChiplet}),",
|
||||
replacement: {
|
||||
match: /("right",)(avatarUrl:null)/,
|
||||
replace: "$1guildId:arguments[0]?.channel?.guild_id,$2"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Pass the guild ID to the profile modal renderer. Allows guild specific profiles
|
||||
// to load on the initial click instead of having to expand the profile first.
|
||||
// Needed specifically for member banners.
|
||||
find: '["children","userId","user"]',
|
||||
replacement: {
|
||||
match: /(\),{)(user:\i,currentUser:\i,children)/,
|
||||
replace: "$1guildId:arguments[0].guildId,$2"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Adds a download button to voice messages before the volume slider.
|
||||
find: "volumeSlider,muted",
|
||||
replacement: {
|
||||
match: /(}\),)(\(0,\i.\i\)\(\i.\i,{className:\i.volumeButton)/,
|
||||
replace: "$1$self.VoiceMessageDownloadButton(arguments[0]),$2"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Hides the inline download button on text files in
|
||||
// favor of the hover download button enabled below.
|
||||
find: "formattedSize),children",
|
||||
replacement: {
|
||||
match: /(\(0,\i.\i\)\(\i.\i,{text:"".concat\(\i.intl.string)/,
|
||||
replace: "false&&$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Passes on the file information to be used by the hover download buttons.
|
||||
find: "downloadUrl,showDownload",
|
||||
replacement: {
|
||||
match: /(showImageAppPicker:\i&&\i)/,
|
||||
replace: "item:arguments[0].item,$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Forces the hover download button to always be visible on supported media.
|
||||
// Also overwrites the onClick function to use the custom download handling.
|
||||
find: "downloadHoverButtonIcon,focusProps:{",
|
||||
replacement: {
|
||||
match: /((\i)=>{)(.{0,60}?)showDownload:(\i),(.{0,1250}?)href:\i,/,
|
||||
replace: "$1const downloadifyHoverItem=$2;$3downloadifyShowDownload:$4=!0,$5onClick:()=>{$self.handleHoverDownloadButtonClicked(downloadifyHoverItem);},"
|
||||
},
|
||||
},
|
||||
{
|
||||
// Overwrites the default download button behavior for expanded image & video modals.
|
||||
// This patch is lazy loaded. You must open an image or video modal for it to resolve.
|
||||
find: "SAVE_MEDIA_PRESSED),",
|
||||
group: true,
|
||||
replacement: [
|
||||
{
|
||||
// Make use of the download function.
|
||||
match: /(let{item:\i}=(\i).{0,450}?)(await \i.\i.saveImage\(\i,\i.contentType,\i.\i\)),/,
|
||||
replace: "$1await $self.handleExpandedModalDownloadButtonClicked($2,async()=>{$3});",
|
||||
},
|
||||
{
|
||||
// Disable default success toast.
|
||||
match: /(\(0,.{0,85}?ToastType.SUCCESS\)\))/,
|
||||
replace: "",
|
||||
},
|
||||
{
|
||||
// Disable default failure toast.
|
||||
match: /(\(0,.{0,85}?ToastType.FAILURE\)\))/,
|
||||
replace: "",
|
||||
},
|
||||
{
|
||||
// Prevent videos from opening in browser.
|
||||
match: /(let{item:\i}=\i.{0,350}?)(SAVE_MEDIA_PRESSED\)).{0,100}?(\i\(!0\);)/,
|
||||
replace: "$1$2,true){$3"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
});
|
||||
94
src/equicordplugins/downloadify/native.ts
Normal file
94
src/equicordplugins/downloadify/native.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { dialog } from "electron";
|
||||
import fs from "fs";
|
||||
import { Readable } from "stream";
|
||||
import { finished } from "stream/promises";
|
||||
|
||||
/**
|
||||
* Check if a file exists at a given path.
|
||||
*/
|
||||
export async function fileExists(_: Electron.IpcMainInvokeEvent, filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.promises.access(filePath, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user to select a directory to save files.
|
||||
*/
|
||||
export async function getDirectory(_: Electron.IpcMainInvokeEvent): Promise<string | null> {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ["openDirectory"]
|
||||
});
|
||||
|
||||
return result.filePaths[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user to select a file name and location to save a file.
|
||||
*/
|
||||
export async function getFilePath(_: Electron.IpcMainInvokeEvent, fileName: string, fileTypes: string[] | null): Promise<string | null> {
|
||||
const filters = fileTypes?.map(ext => ({
|
||||
name: ext.toUpperCase(),
|
||||
extensions: [ext]
|
||||
})) ?? [];
|
||||
|
||||
if (!fileTypes?.length) {
|
||||
filters.push({ name: "All Files", extensions: ["*"] });
|
||||
}
|
||||
|
||||
const result = await dialog.showSaveDialog({
|
||||
title: "Save File",
|
||||
defaultPath: fileName,
|
||||
buttonLabel: "Save",
|
||||
properties: ["createDirectory"],
|
||||
filters
|
||||
});
|
||||
|
||||
return result.filePath ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content type of a file at a URL.
|
||||
*
|
||||
* @returns The content type of the file if it exists, undefined if no content type is available, or null if the file doesn't exist.
|
||||
*/
|
||||
export async function queryURL(_: Electron.IpcMainInvokeEvent, url: string): Promise<string | null | undefined> {
|
||||
const response = await fetch(url, { method: "HEAD" });
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.headers.get("content-type") ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from a URL to a local file path.
|
||||
*/
|
||||
export async function downloadURL(_: Electron.IpcMainInvokeEvent, url: string, filePath: string): Promise<boolean> {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const readableStream = Readable.fromWeb(response.body as any);
|
||||
const fileWriteStream = fs.createWriteStream(filePath);
|
||||
readableStream.pipe(fileWriteStream);
|
||||
await finished(fileWriteStream);
|
||||
|
||||
return true;
|
||||
}
|
||||
109
src/equicordplugins/downloadify/settings.tsx
Normal file
109
src/equicordplugins/downloadify/settings.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { OptionType } from "@utils/types";
|
||||
import { Button, Forms, showToast, Toasts, useState } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { d, DownloadifyLogger, DownloadifyNative } from "./utils/definitions";
|
||||
import { getFormattedNow } from "./utils/misc";
|
||||
|
||||
function DefaultDirectorySetting(): JSX.Element {
|
||||
const { defaultDirectory } = settings.use(["defaultDirectory"]);
|
||||
const [isDialogueOpen, setDialogueOpen] = useState(false);
|
||||
|
||||
const handlePickDirectory = async () => {
|
||||
try {
|
||||
setDialogueOpen(true);
|
||||
const directory = await DownloadifyNative.getDirectory();
|
||||
directory && (settings.store.defaultDirectory = directory);
|
||||
} catch (error) {
|
||||
DownloadifyLogger.error(`[${getFormattedNow()}] [FAILED TO SET DOWNLOAD DIRECTORY]`, error);
|
||||
showToast("Failed to set download directory.", Toasts.Type.FAILURE, { duration: 3000 });
|
||||
} finally {
|
||||
setDialogueOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearDirectory = () => {
|
||||
settings.store.defaultDirectory = "";
|
||||
};
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle className={d("form-title")}>
|
||||
Default Directory
|
||||
</Forms.FormTitle>
|
||||
<Forms.FormText className={d("form-description")}>
|
||||
Default download location. If set, the file will always be downloaded in its original format even if alternatives are available. Leave empty to pick a folder and file type each time.
|
||||
</Forms.FormText>
|
||||
<div className={d("directory-container")}>
|
||||
<Forms.FormText className={d("directory-display")}>
|
||||
{defaultDirectory || "No Directory Set"}
|
||||
</Forms.FormText>
|
||||
<div className={d("directory-buttons")}>
|
||||
<Button
|
||||
disabled={isDialogueOpen}
|
||||
className={d("directory-button", "browse-button", { "disabled": isDialogueOpen })}
|
||||
onClick={handlePickDirectory}
|
||||
color={Button.Colors.CUSTOM}
|
||||
>
|
||||
{isDialogueOpen ? "Browsing..." : "Browse"}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!defaultDirectory || isDialogueOpen}
|
||||
className={d("directory-button", "clear-button", { "disabled": !defaultDirectory || isDialogueOpen })}
|
||||
onClick={handleClearDirectory}
|
||||
color={Button.Colors.CUSTOM}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Forms.FormSection>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
displayStatus: {
|
||||
description: "Display a status notification when downloads start, finish, or error.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
statusDuration: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "The number of seconds to display status notifications.",
|
||||
markers: [1, 3, 5],
|
||||
default: 2.5,
|
||||
stickToMarkers: false,
|
||||
},
|
||||
voiceMessages: {
|
||||
description: "Add a download button to voice messages.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
allowUnicode: {
|
||||
description: "Allow non-ASCII characters in file names.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
overwriteFiles: {
|
||||
description: "If a default directory is set and a download file name matches an existing file, overwrite the file. If disabled, a number will be appended to the file name.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
},
|
||||
defaultDirectory: {
|
||||
component: DefaultDirectorySetting,
|
||||
type: OptionType.COMPONENT,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
77
src/equicordplugins/downloadify/style.css
Normal file
77
src/equicordplugins/downloadify/style.css
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
.downloadify-voice-message-container {
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
position: relative;
|
||||
margin: 0 -2px;
|
||||
}
|
||||
|
||||
.downloadify-voice-message-button {
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
border: none;
|
||||
width: auto;
|
||||
background: transparent;
|
||||
color: var(--interactive-normal);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition-duration: .2s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.downloadify-voice-message-button:hover {
|
||||
color: var(--interactive-hover);
|
||||
}
|
||||
|
||||
.downloadify-directory-container {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.downloadify-directory-buttons {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.downloadify-directory-button {
|
||||
border-radius: 4px;
|
||||
min-height: 34px;
|
||||
color: white;
|
||||
height: 34px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.downloadify-directory-button:not(.downloadify-disabled):hover {
|
||||
filter: brightness(0.75);
|
||||
transition: filter 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.downloadify-clear-button {
|
||||
background-color: rgb(180 71 78);
|
||||
}
|
||||
|
||||
.downloadify-browse-button {
|
||||
background-color: rgb(87 100 240);
|
||||
}
|
||||
|
||||
.downloadify-directory-display {
|
||||
background-color: var(--background-base-lower);
|
||||
overflow-wrap: break-word;
|
||||
font-family: monospace;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
border-radius: 4px;
|
||||
user-select: text;
|
||||
font-size: 14px;
|
||||
padding: 8px;
|
||||
}
|
||||
371
src/equicordplugins/downloadify/utils/definitions.tsx
Normal file
371
src/equicordplugins/downloadify/utils/definitions.tsx
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { PluginNative } from "@utils/types";
|
||||
import { Channel, Guild, GuildMember, Message, User } from "@vencord/discord-types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { JSX } from "react";
|
||||
|
||||
export const d = classNameFactory("downloadify-");
|
||||
export const CDN_BASE = "https://cdn.discordapp.com";
|
||||
export const MEDIA_PROXY_BASE = "https://media.discordapp.net";
|
||||
export const PRIMARY_DOMAIN_BASE = "https://discord.com";
|
||||
export const IMAGE_EXT_1_DOMAIN_BASE = "https://images-ext-1.discordapp.net";
|
||||
export const IMAGE_EXT_2_DOMAIN_BASE = "https://images-ext-2.discordapp.net";
|
||||
export const TENOR_BASE = "https://c.tenor.com";
|
||||
export const TENOR_GIF_ID = "Ad";
|
||||
export const TENOR_MP4_ID = "Po";
|
||||
export const ASSET_TYPE_EXTRACTOR = new RegExp(/^https:\/\/(?:cdn\.discordapp\.com|media.discordapp.net)\/([a-z-]+(?:\/[a-z-]+){0,2})(?:(?:\/\d+\/[a-z-]+\/\d+\/([a-z-]+))|(?:\/([a-z-]+(?:\/[a-z-_]+)?)))?/i);
|
||||
export const DownloadifyLogger = new Logger("Downloadify");
|
||||
export const DownloadifyNative = VencordNative.pluginHelpers.Downloadify as PluginNative<typeof import("../native")>;
|
||||
export const defaultAssets = findByPropsLazy("DEFAULT_GROUP_DM_AVATARS") as DefaultAssets;
|
||||
|
||||
interface DefaultAssets {
|
||||
BOT_AVATARS: Record<string, string>;
|
||||
DEFAULT_AVATARS: string[];
|
||||
DEFAULT_CHANNEL_ICON: string;
|
||||
DEFAULT_GROUP_DM_AVATARS: string[];
|
||||
DEFAULT_PROVISIONAL_AVATARS: string[];
|
||||
}
|
||||
|
||||
export interface AssetInfo {
|
||||
source: AssetSource;
|
||||
static: string[];
|
||||
animated: string[];
|
||||
type?: string;
|
||||
forceAnimated?: boolean;
|
||||
}
|
||||
|
||||
export enum AssetSource {
|
||||
CDN = "cdn",
|
||||
EXTERNAL_IMAGE_PROXY = "external_image_proxy",
|
||||
ATTACHMENT_MEDIA_PROXY = "attachment_media_proxy",
|
||||
ASSET_MEDIA_PROXY = "asset_media_proxy",
|
||||
PRIMARY_DOMAIN = "primary_domain",
|
||||
EXTERNAL = "external",
|
||||
TENOR = "tenor"
|
||||
}
|
||||
|
||||
export const unknownExternal = {
|
||||
source: AssetSource.EXTERNAL,
|
||||
static: [],
|
||||
animated: []
|
||||
};
|
||||
|
||||
export const unknownPrimaryDomain = {
|
||||
source: AssetSource.PRIMARY_DOMAIN,
|
||||
static: [],
|
||||
animated: []
|
||||
};
|
||||
|
||||
export const unknownCDN = {
|
||||
source: AssetSource.CDN,
|
||||
static: [],
|
||||
animated: []
|
||||
};
|
||||
|
||||
export const unknownAssetMediaProxy = {
|
||||
source: AssetSource.ASSET_MEDIA_PROXY,
|
||||
static: [],
|
||||
animated: []
|
||||
};
|
||||
|
||||
export const unknownAttachmentMediaProxy = {
|
||||
source: AssetSource.ATTACHMENT_MEDIA_PROXY,
|
||||
static: [],
|
||||
animated: []
|
||||
};
|
||||
|
||||
export const unknownExternalImageProxy = {
|
||||
source: AssetSource.EXTERNAL_IMAGE_PROXY,
|
||||
static: [],
|
||||
animated: []
|
||||
};
|
||||
|
||||
export const defaultResourceAvailability = {
|
||||
source: AssetSource.PRIMARY_DOMAIN,
|
||||
static: ["png"],
|
||||
animated: []
|
||||
};
|
||||
|
||||
export const APNGAvailability = {
|
||||
source: AssetSource.ASSET_MEDIA_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
animated: ["apng", "png", "webp", "jpg"]
|
||||
};
|
||||
|
||||
export const hashableAvailability = {
|
||||
source: AssetSource.ASSET_MEDIA_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
animated: ["gif", "awebp", "png", "webp", "jpg"]
|
||||
};
|
||||
|
||||
// Defines known asset type availability. Unknown types which may be
|
||||
// encountered are AssetSource.EXTERNAL which are present in some embeds,
|
||||
// as well as OTHER attachment types.
|
||||
export const assetAvailability = {
|
||||
"user-avatar": hashableAvailability,
|
||||
"user-banner": hashableAvailability,
|
||||
"avatar-decoration": APNGAvailability,
|
||||
"clan-badge": {
|
||||
source: AssetSource.ASSET_MEDIA_PROXY,
|
||||
static: ["png"],
|
||||
animated: []
|
||||
},
|
||||
"nameplate": {
|
||||
source: AssetSource.CDN,
|
||||
static: ["png"],
|
||||
animated: ["webm", "apng", "png"],
|
||||
type: "nameplate"
|
||||
},
|
||||
"default-user-avatar": defaultResourceAvailability,
|
||||
"default-group-icon": defaultResourceAvailability,
|
||||
"role-icon-custom": {
|
||||
source: AssetSource.ASSET_MEDIA_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
animated: [],
|
||||
},
|
||||
"role-icon-unicode-emoji": {
|
||||
source: AssetSource.PRIMARY_DOMAIN,
|
||||
static: ["svg"],
|
||||
animated: [],
|
||||
},
|
||||
"guild-icon": hashableAvailability,
|
||||
"guild-banner": hashableAvailability,
|
||||
"guild-invite-splash": hashableAvailability,
|
||||
"guild-discovery-splash": hashableAvailability,
|
||||
"custom-emoji": hashableAvailability,
|
||||
"unicode-emoji": {
|
||||
source: AssetSource.PRIMARY_DOMAIN,
|
||||
static: ["svg"],
|
||||
animated: []
|
||||
},
|
||||
"sticker": {
|
||||
"APNG": APNGAvailability,
|
||||
"PNG": {
|
||||
source: AssetSource.ASSET_MEDIA_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
animated: []
|
||||
},
|
||||
"GIF": {
|
||||
source: AssetSource.ASSET_MEDIA_PROXY,
|
||||
static: [],
|
||||
animated: ["gif", "awebp", "png", "webp", "jpg"],
|
||||
forceAnimated: true
|
||||
},
|
||||
"LOTTIE": {
|
||||
source: AssetSource.CDN,
|
||||
static: ["json"],
|
||||
animated: ["json"]
|
||||
},
|
||||
},
|
||||
"tenor": {
|
||||
source: AssetSource.TENOR,
|
||||
static: [],
|
||||
animated: ["gif", "mp4"],
|
||||
forceAnimated: true
|
||||
},
|
||||
"external": {
|
||||
"image/png": {
|
||||
source: AssetSource.EXTERNAL_IMAGE_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
animated: []
|
||||
},
|
||||
"image/webp": {
|
||||
source: AssetSource.EXTERNAL_IMAGE_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
animated: []
|
||||
},
|
||||
"image/jpeg": {
|
||||
source: AssetSource.EXTERNAL_IMAGE_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
animated: []
|
||||
},
|
||||
"video/mp4": {
|
||||
source: AssetSource.EXTERNAL,
|
||||
static: [],
|
||||
animated: ["mp4"],
|
||||
forceAnimated: true
|
||||
},
|
||||
},
|
||||
"attachment": {
|
||||
// Image attachments over a certain size and/or resolution are treated as
|
||||
// non-image files and are therefore not available in alternative formats.
|
||||
// This is designated by an attachment type of OTHER instead of IMAGE.
|
||||
"image/png": {
|
||||
source: AssetSource.ATTACHMENT_MEDIA_PROXY,
|
||||
static: ["png", "webp", "jpg"],
|
||||
// APNG attachments are technically supported, but appear to get
|
||||
// broken by Discord squashing them into static PNGs. APNG is kept
|
||||
// here in case it is fixed some day.
|
||||
animated: ["apng", "png", "webp", "jpg"]
|
||||
},
|
||||
"image/webp": {
|
||||
source: AssetSource.ATTACHMENT_MEDIA_PROXY,
|
||||
static: ["webp", "png", "jpg"],
|
||||
animated: ["awebp", "webp", "png", "jpg"]
|
||||
},
|
||||
"image/avif": {
|
||||
source: AssetSource.ATTACHMENT_MEDIA_PROXY,
|
||||
static: ["avif", "png", "webp"],
|
||||
animated: ["avif", "awebp", "png", "webp"]
|
||||
},
|
||||
"image/jpeg": {
|
||||
source: AssetSource.ATTACHMENT_MEDIA_PROXY,
|
||||
static: ["jpg", "png", "webp"],
|
||||
animated: []
|
||||
},
|
||||
"image/gif": {
|
||||
source: AssetSource.ATTACHMENT_MEDIA_PROXY,
|
||||
static: [],
|
||||
animated: ["gif", "awebp", "png", "webp", "jpg"],
|
||||
forceAnimated: true
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export function DownloadIcon({ width, height }: { width: number; height: number; }): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
className={d("-download-icon")}
|
||||
role="img"
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 2a1 1 0 0 1 1 1v10.59l3.3-3.3a1 1 0 1 1 1.4 1.42l-5 5a1 1 0 0 1-1.4 0l-5-5a1 1 0 1 1 1.4-1.42l3.3 3.3V3a1 1 0 0 1 1-1ZM3 20a1 1 0 1 0 0 2h18a1 1 0 1 0 0-2H3Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ParsedFile {
|
||||
path: string;
|
||||
baseName: string;
|
||||
extension: string | null;
|
||||
twitterExtra: string | null;
|
||||
}
|
||||
|
||||
export interface ParsedURL {
|
||||
url: string;
|
||||
host: string;
|
||||
path: string;
|
||||
baseName: string;
|
||||
extension: string | null;
|
||||
params: { expiry: Record<string, string>, other: Record<string, string>; };
|
||||
twitterExtra: string | null;
|
||||
}
|
||||
|
||||
export interface InsertGroup {
|
||||
id: { group?: string, child?: string; };
|
||||
type: "WITH_GROUP" | "BEFORE_GROUP" | "AFTER_GROUP";
|
||||
position?: "START" | "END";
|
||||
}
|
||||
|
||||
export interface VoiceMessageDownloadButtonProps {
|
||||
message: Message;
|
||||
item: {
|
||||
type: "AUDIO";
|
||||
originalItem: {
|
||||
filename: string;
|
||||
proxy_url: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface HoverDownloadProps {
|
||||
item: {
|
||||
type: "AUDIO" | "IMAGE" | "VIDEO" | "OTHER";
|
||||
downloadUrl: string;
|
||||
contentType: string;
|
||||
srcIsAnimated: boolean;
|
||||
originalItem: {
|
||||
title?: string;
|
||||
filename: string;
|
||||
proxy_url: string;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExpandedModalDownloadProps {
|
||||
item: {
|
||||
type: "AUDIO" | "IMAGE" | "VIDEO" | "OTHER";
|
||||
url: string;
|
||||
original: string;
|
||||
proxyUrl?: string;
|
||||
contentType?: string;
|
||||
srcIsAnimated: boolean;
|
||||
sourceMetadata?: {
|
||||
identifier: {
|
||||
type: "attachment" | "embed";
|
||||
title?: string;
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageContextMenuProps {
|
||||
message: Message;
|
||||
channel: Channel;
|
||||
itemSrc: string;
|
||||
itemSafeSrc?: string;
|
||||
favoriteableType: "sticker" | "emoji" | null;
|
||||
favoriteableName: string | null;
|
||||
favoriteableId: string | null;
|
||||
contextMenuAPIArguments: any[];
|
||||
mediaItem?: {
|
||||
contentType: string;
|
||||
proxyUrl: string;
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GDMContextMenuProps {
|
||||
channel: Channel & {
|
||||
icon: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildContextMenuProps {
|
||||
guild: Guild;
|
||||
}
|
||||
|
||||
export interface UserContextMenuProps {
|
||||
user: User;
|
||||
guildId?: string;
|
||||
}
|
||||
|
||||
export interface CustomizedUser extends User {
|
||||
avatarDecorationData: null | {
|
||||
asset: string;
|
||||
};
|
||||
collectibles: null | Record<"nameplate", {
|
||||
asset: string;
|
||||
label: string;
|
||||
palette: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface CustomizedMember extends Omit<GuildMember, "avatarDecoration"> {
|
||||
avatarDecoration: null | {
|
||||
asset: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const AttachmentFlags = {
|
||||
IS_CLIP: 1 << 0,
|
||||
IS_THUMBNAIL: 1 << 1,
|
||||
IS_REMIX: 1 << 2,
|
||||
IS_SPOILER: 1 << 3,
|
||||
CONTAINS_EXPLICIT_MEDIA: 1 << 4,
|
||||
IS_ANIMATED: 1 << 5,
|
||||
CONTAINS_GORE_CONTENT: 1 << 6
|
||||
};
|
||||
1006
src/equicordplugins/downloadify/utils/handlers.tsx
Normal file
1006
src/equicordplugins/downloadify/utils/handlers.tsx
Normal file
File diff suppressed because it is too large
Load diff
253
src/equicordplugins/downloadify/utils/misc.tsx
Normal file
253
src/equicordplugins/downloadify/utils/misc.tsx
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId } from "@api/ContextMenu";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
||||
import { InsertGroup, ParsedFile, ParsedURL } from "./definitions";
|
||||
|
||||
/**
|
||||
* Retrieve information about a file path by parsing its parts.
|
||||
*/
|
||||
export function parseFile(filePath: string): ParsedFile {
|
||||
const parts = filePath.split(/[\\/]/);
|
||||
let fileNameWithExtra = parts.pop() as string;
|
||||
|
||||
while (fileNameWithExtra.includes("%25")) {
|
||||
fileNameWithExtra = decodeURIComponent(fileNameWithExtra);
|
||||
}
|
||||
|
||||
fileNameWithExtra = decodeURIComponent(fileNameWithExtra);
|
||||
|
||||
const posColon = fileNameWithExtra.indexOf(":");
|
||||
const fileNameWithoutExtra = posColon === -1 ? fileNameWithExtra : fileNameWithExtra.slice(0, posColon);
|
||||
const twitterExtra = posColon === -1 ? null : fileNameWithExtra.slice(posColon);
|
||||
|
||||
const path = parts.join("/") + "/";
|
||||
const posDot = fileNameWithoutExtra.lastIndexOf(".");
|
||||
|
||||
if (fileNameWithoutExtra === "" || posDot < 1) {
|
||||
return {
|
||||
path,
|
||||
baseName: fileNameWithoutExtra,
|
||||
extension: null,
|
||||
twitterExtra: null
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
baseName: fileNameWithoutExtra.slice(0, posDot),
|
||||
extension: fileNameWithoutExtra.slice(posDot + 1).toLowerCase(),
|
||||
twitterExtra: twitterExtra ?? null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve information about a URL by parsing its parts.
|
||||
*/
|
||||
export function parseURL(url: string): ParsedURL {
|
||||
const parsed = new URL(url);
|
||||
const { path, baseName, extension, twitterExtra } = parseFile(parsed.pathname);
|
||||
const params = Array.from(parsed.searchParams.entries()).reduce(
|
||||
(acc, [key, value]) => {
|
||||
if (["ex", "is", "hm"].includes(key)) {
|
||||
acc.expiry[key] = value;
|
||||
} else {
|
||||
acc.other[key] = value;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, { expiry: {}, other: {} } as { expiry: Record<string, string>, other: Record<string, string>; }
|
||||
);
|
||||
|
||||
return {
|
||||
url: url,
|
||||
host: parsed.origin.toLowerCase(),
|
||||
path,
|
||||
baseName,
|
||||
extension,
|
||||
params,
|
||||
twitterExtra
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a file name by replacing invalid characters with underscores.
|
||||
*/
|
||||
export function sanitizeFilename(filename: string, allowUnicode: boolean, fallback?: string): string {
|
||||
let sanitized = filename.replace(/[<>:"/\\|?*\x00-\x1F]/g, "_") // windows-reserved
|
||||
.replace(/^\./, "_") // dot files
|
||||
.replace(/(\.\.)+/g, "_") // relative paths
|
||||
.replace(/[ \t\n\r]+/g, "_"); // spaces
|
||||
|
||||
if (!allowUnicode) {
|
||||
sanitized = sanitized.replace(/[^\x00-\x7F]/g, "_"); // non-ASCII characters
|
||||
} else {
|
||||
sanitized = sanitized.normalize("NFD"); // normalize unicode
|
||||
}
|
||||
|
||||
sanitized = sanitized.replace(/^_+|_+$/g, ""); // leading and trailing underscores
|
||||
return sanitized || fallback || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date and time formatted as a string.
|
||||
*/
|
||||
export function getFormattedNow(): string {
|
||||
return new Date().toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: true
|
||||
}).replace(",", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Join or create a group in the guild context menu.
|
||||
*/
|
||||
export function joinOrCreateContextMenuGroup(
|
||||
children: Array<any>,
|
||||
items: Array<any>,
|
||||
groupId: string,
|
||||
submenuId?: string,
|
||||
submenuLabel?: string,
|
||||
insertOrder?: InsertGroup[]
|
||||
) {
|
||||
function joinOrCreateSubmenu() {
|
||||
const existingItemShouldJoinSubmenu = existingGroup.props.children.find(child => child?.props?.submenuId === submenuId);
|
||||
let existingSubmenu = existingGroup.props.children.find(child => child?.props?.id === submenuId);
|
||||
let numitems = items.length;
|
||||
|
||||
if (existingItemShouldJoinSubmenu) {
|
||||
existingSubmenu = null;
|
||||
items.unshift(existingItemShouldJoinSubmenu);
|
||||
numitems++;
|
||||
const indexOfExistingItemShouldJoinSubmenu = existingGroup.props.children.findIndex(child => child === existingItemShouldJoinSubmenu);
|
||||
existingGroup.props.children.splice(indexOfExistingItemShouldJoinSubmenu, 1);
|
||||
}
|
||||
|
||||
if (existingSubmenu) {
|
||||
if (!Array.isArray(existingSubmenu.props.children)) {
|
||||
existingSubmenu.props.children = [existingSubmenu.props.children];
|
||||
}
|
||||
|
||||
items.forEach(member => member.props.label = member.props.submenuItemLabel ?? member.props.label);
|
||||
existingSubmenu.props.children.push(...items);
|
||||
} else if (numitems === 1) {
|
||||
if (!chosenInsertOption?.position || chosenInsertOption.position === "END") {
|
||||
existingGroup.props.children.push(items[0]);
|
||||
} else {
|
||||
existingGroup.props.children.unshift(items[0]);
|
||||
}
|
||||
} else {
|
||||
existingSubmenu = (
|
||||
<Menu.MenuItem
|
||||
id={submenuId as string}
|
||||
label={submenuLabel}
|
||||
>
|
||||
{[...items]}
|
||||
</Menu.MenuItem>
|
||||
);
|
||||
|
||||
items.forEach(member => member.props.label = member.props.submenuItemLabel ?? member.props.label);
|
||||
|
||||
if (!chosenInsertOption?.position || chosenInsertOption.position === "END") {
|
||||
existingGroup.props.children.push(existingSubmenu);
|
||||
} else {
|
||||
existingGroup.props.children.unshift(existingSubmenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.forEach(member => {
|
||||
member.props.groupId = groupId;
|
||||
member.props.submenuId = submenuId;
|
||||
member.props.submenuLabel = submenuLabel;
|
||||
});
|
||||
|
||||
const insertWithOptions = insertOrder?.filter(item => item.type === "WITH_GROUP");
|
||||
let existingGroup = children.find(child => child?.props?.id === groupId);
|
||||
let chosenInsertOption;
|
||||
|
||||
if (!existingGroup && insertWithOptions) {
|
||||
let targetItem;
|
||||
|
||||
for (const item of insertWithOptions) {
|
||||
if (item.id.group) {
|
||||
targetItem = children.find(child => child?.props?.id === item.id.group);
|
||||
}
|
||||
|
||||
if (!targetItem && item.id.child) {
|
||||
targetItem = findGroupChildrenByChildId(item.id.child, children, true);
|
||||
}
|
||||
|
||||
if (targetItem) {
|
||||
existingGroup = children.find(child => child?.props?.children === targetItem);
|
||||
chosenInsertOption = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existingGroup) {
|
||||
if (!Array.isArray(existingGroup.props.children)) {
|
||||
existingGroup.props.children = [existingGroup.props.children];
|
||||
}
|
||||
|
||||
if (!submenuId) {
|
||||
if (!chosenInsertOption?.position || chosenInsertOption.position === "END") {
|
||||
existingGroup.props.children.push(...items);
|
||||
} else {
|
||||
existingGroup.props.children.unshift(...items);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
joinOrCreateSubmenu();
|
||||
}
|
||||
} else {
|
||||
if (!submenuId) {
|
||||
existingGroup = (
|
||||
<Menu.MenuGroup id={groupId}>
|
||||
{[...items]}
|
||||
</Menu.MenuGroup>
|
||||
);
|
||||
} else {
|
||||
existingGroup = (<Menu.MenuGroup id={groupId}>{[]}</Menu.MenuGroup>);
|
||||
joinOrCreateSubmenu();
|
||||
}
|
||||
|
||||
const insertAfterBeforeOptions = insertOrder?.filter(item => item.type !== "WITH_GROUP") || [];
|
||||
let targetIndex = children.length;
|
||||
let targetItem;
|
||||
|
||||
for (const item of insertAfterBeforeOptions) {
|
||||
if (item.id.group) {
|
||||
targetItem = children.find(child => child?.props?.id === item.id.group);
|
||||
}
|
||||
|
||||
if (!targetItem && item.id.child) {
|
||||
targetItem = findGroupChildrenByChildId(item.id.child, children, true);
|
||||
}
|
||||
|
||||
if (targetItem) {
|
||||
targetIndex = children.findIndex(child => child?.props?.children === targetItem);
|
||||
|
||||
if (item.type === "AFTER_GROUP") {
|
||||
targetIndex++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
children.splice(targetIndex, 0, existingGroup);
|
||||
}
|
||||
}
|
||||
3
src/equicordplugins/equicordHelper/README.md
Normal file
3
src/equicordplugins/equicordHelper/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# EquicordHelper
|
||||
|
||||
Fixes various issues for crashing and adds other items that might help with support
|
||||
3
src/equicordplugins/equicordToolbox/README.md
Normal file
3
src/equicordplugins/equicordToolbox/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# EquicordToolbox
|
||||
|
||||
Adds a toolbox for various control options
|
||||
|
|
@ -165,7 +165,7 @@ export default definePlugin({
|
|||
{
|
||||
find: ".controlButtonWrapper,",
|
||||
replacement: {
|
||||
match: /(?<=toolbar:function\(.{0,100}\()\i.Fragment,/,
|
||||
match: /(?<=function (\i).{0,100}\()\i.Fragment,(?=.+?toolbar:\1\(\))/,
|
||||
replace: "$self.ToolboxFragmentWrapper,"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/equicordplugins/equissant/README.md
Normal file
3
src/equicordplugins/equissant/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Equissant
|
||||
|
||||
Plays crossant every 10 clicks
|
||||
3
src/equicordplugins/exportContacts/README.md
Normal file
3
src/equicordplugins/exportContacts/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ExportContacts
|
||||
|
||||
Exports your friends list onto your clipboard
|
||||
3
src/equicordplugins/exportMessages/README.md
Normal file
3
src/equicordplugins/exportMessages/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ExportMessages
|
||||
|
||||
Allows exporting a message to a file
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# FakeProfileThemesAndEffects
|
||||
|
||||
Expanded FakeProfileThemes with effects
|
||||
3
src/equicordplugins/fastDeleteChannels/README.md
Normal file
3
src/equicordplugins/fastDeleteChannels/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FastDeleteChannels
|
||||
|
||||
Adds a trash can icon when pressing specific keybinds to delete a channel/channels instantly
|
||||
5
src/equicordplugins/findReply/README.md
Normal file
5
src/equicordplugins/findReply/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# FindReply
|
||||
|
||||
Lets you jump to the replies to a message. Makes it easier to follow past conversations/Q&A's.
|
||||
|
||||

|
||||
3
src/equicordplugins/fixFileExtensions/README.MD
Normal file
3
src/equicordplugins/fixFileExtensions/README.MD
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FixFileExtensions
|
||||
|
||||
Fixes file extensions by renaming them to work on discord
|
||||
3
src/equicordplugins/followVoiceUser/README.md
Normal file
3
src/equicordplugins/followVoiceUser/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FollowVoiceUser
|
||||
|
||||
Allows following a friend through voice chats
|
||||
3
src/equicordplugins/fontLoader/README.md
Normal file
3
src/equicordplugins/fontLoader/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FontLoader
|
||||
|
||||
Allows loading and using different fonts from Google
|
||||
3
src/equicordplugins/forwardAnywhere/README.md
Normal file
3
src/equicordplugins/forwardAnywhere/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ForwardAnywhere
|
||||
|
||||
Allows forwarding a message anywhere
|
||||
3
src/equicordplugins/freaky/README.md
Normal file
3
src/equicordplugins/freaky/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Freaky
|
||||
|
||||
Changes your text to look like this 𝓔𝓺𝓾𝓲𝓬𝓸𝓻𝓭
|
||||
3
src/equicordplugins/frequentQuickSwitcher/README.md
Normal file
3
src/equicordplugins/frequentQuickSwitcher/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FrequentQuickSwitcher
|
||||
|
||||
Changes quick switcher to be your most frequent channels
|
||||
7
src/equicordplugins/friendCloud/README.md
Normal file
7
src/equicordplugins/friendCloud/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# FriendCloud
|
||||
|
||||
Visualize the users you interact with most in a cloud!
|
||||
|
||||
Simply run the `/friendcloud` command to generate a cloud of your friends based on your interactions with them.
|
||||
|
||||
Use the `count` option to set the number of users to display.
|
||||
3
src/equicordplugins/friendCodes/README.md
Normal file
3
src/equicordplugins/friendCodes/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FriendCodes
|
||||
|
||||
Easily generate friend codes
|
||||
3
src/equicordplugins/friendTags/README.md
Normal file
3
src/equicordplugins/friendTags/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FriendTags
|
||||
|
||||
Allows filtering friends in quick switcher
|
||||
3
src/equicordplugins/friendshipRanks/README.md
Normal file
3
src/equicordplugins/friendshipRanks/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FriendshipRanks
|
||||
|
||||
Adds badges to show how long you've been friends with a user
|
||||
3
src/equicordplugins/fullVcPfp/README.md
Normal file
3
src/equicordplugins/fullVcPfp/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FullVCPFP
|
||||
|
||||
Makes profile pictures show fully
|
||||
5
src/equicordplugins/ignoreTerms/README.md
Normal file
5
src/equicordplugins/ignoreTerms/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# IgnoreTerms
|
||||
|
||||
Ignore Discord's new terms of service
|
||||
|
||||

|
||||
3
src/equicordplugins/messageLinkTooltip/README.md
Normal file
3
src/equicordplugins/messageLinkTooltip/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# MessageLinkTooltip
|
||||
|
||||
MessageLinkEmbed but with space constrants
|
||||
|
|
@ -36,8 +36,8 @@ export default definePlugin({
|
|||
{
|
||||
find: "renderLurkerModeUpsellPopout,position:",
|
||||
replacement: {
|
||||
match: /200:300/g,
|
||||
replace: "0:0",
|
||||
match: /(\i)=(200|300)/g,
|
||||
replace: "$1=0",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# NotificationTitle
|
||||
|
||||
Improved Desktop Notifications
|
||||
|
|
@ -262,14 +262,14 @@ export default definePlugin({
|
|||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "BkZhUF)}",
|
||||
find: "BkZhUF),",
|
||||
replacement: {
|
||||
match: /(?<=questId:(\i\.id).*?\.PRIMARY,)disabled:!0/,
|
||||
match: /(?<=questId:(\i\.id).*?"secondary",)disabled:!0/,
|
||||
replace: "onClick: () => $self.mobileQuestPatch($1)"
|
||||
},
|
||||
},
|
||||
{
|
||||
find: "AppTitleBar",
|
||||
find: '"BACK_FORWARD_NAVIGATION"',
|
||||
replacement: {
|
||||
match: /(?<=trailing:.{0,70}\(\i\.Fragment,{children:\[)/,
|
||||
replace: "$self.renderQuestButton(),"
|
||||
|
|
|
|||
68
src/equicordplugins/questify/README.md
Normal file
68
src/equicordplugins/questify/README.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Questify
|
||||
|
||||
Enhance your Quest experience with a suite of features, or disable them entirely if they're not your thing.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
1. Quest Features
|
||||
- Modify specific Quest features.
|
||||
- Allows for disabling the following Quest related features:
|
||||
- Everything — All of the below is disabled.
|
||||
- Discovery Tab — Quests tab becomes hidden.
|
||||
- Fetching Quests — Will prevent fetching Quests resulting in a blank Quests page.
|
||||
- Badge on User Profiles — Hides the profile badge which doubles as a shortcut to the Quests page.
|
||||
- Popup Above User Panel — Hides the new Quest promotion that appears above your profile panel.
|
||||
- Gift Inventory Relocation Notice — Hides the text in the gift inventory which doubles as a shortcut to the Quests page.
|
||||
- Friends List Active Now Promotion — Hides the promotion that displays when your friends play a game which has an active Quest.
|
||||
- Allows for modifying the following Quest related features:
|
||||
- Complete Video Quests in Background — Click the watch button on video Quests and wait for the video duration to pass. The first click will start the Quest without opening the video modal. Subsequent clicks will show the modal as normal.
|
||||
- Complete Game Quests in Background — Click the accept button on game Quests and wait for the play duration to pass.
|
||||
- Make Mobile Quests Desktop Compatible — Make mobile-only video Quests completable on Desktop.
|
||||
2. Quests Button
|
||||
- Show a Quest button in the server list with an optional indicator for unclaimed Quests.
|
||||
- Button Visibility
|
||||
- Always — Always display the button.
|
||||
- Unclaimed — Only display the button when you have unclaimed Quests.
|
||||
- Never — Never display the button.
|
||||
- Unclaimed Indicator
|
||||
- Pill — Shows an "unread" pill when there are unclaimed Quests.
|
||||
- Badge — Shows the number of unclaimed Quests as a "ping" badge.
|
||||
- Both — Both of the above.
|
||||
- None — Show no indication of unclaimed Quests.
|
||||
- Badge Color
|
||||
- Use the color picker to pick a custom badge color or use the default Discord ping badge color by clicking `Disable`.
|
||||
- Left / Middle / Right Click Actions
|
||||
- Open Quests — Navigate to the Quests tab in Discovery.
|
||||
- Plugin Settings — Open the plugin settings.
|
||||
- Context Menu — Show the options `Mark All Ignored`, `Reset Ignored List`, and `Fetch Quests`.
|
||||
- Nothing — Do nothing.
|
||||
3. Fetching Quests
|
||||
- Configure how often to fetch Quests from Discord and set up alerts for new Quests.
|
||||
- By default, Discord only fetches Quests on load and when visiting the Quests page. This means that without a fetch interval defined below, this plugin will become unaware of new Quests added throughout the day.
|
||||
- This relies on the Quest Button being enabled and set to either `Unclaimed`, or set to `Always` with unclaimed `Pill`, `Badge`, or `Both` indicators enabled. Otherwise, there is no reason to periodically fetch Quests.
|
||||
- Also, if `Fetching Quests` is blocked in the `Quest Features` setting, this will not work.
|
||||
- Fetch Interval
|
||||
- Any time between 30 minutes and 12 hours. Defaults to every 45 minutes.
|
||||
- Alert Sound
|
||||
- Pick a default Discord sound to play when new Quests are fetched, or provide a custom audio URL. The audio must be hosted on a domain whitelisted by the Vencord CSP, such as `catbox`.
|
||||
- If the provided URL is not on a supported domain, you will receive an error. If it *is* on a supported domain, but the audio file is invalid, it will silently fail. To ensure your audio file plays fine, click the audio preview button in the plugin settings.
|
||||
4. Restyle Quests
|
||||
- Highlight Quests with optional theme colors for visibility.
|
||||
- Unclaimed / Claimed / Ignored / Expired
|
||||
- Use the color picker to pick a custom Quest tile color or use the default Discord coloring by clicking `Disable`.
|
||||
- Gradient Style
|
||||
- Intense Restyle Gradient — A more intense gradient than default in order to hide hard edges under the gradient.
|
||||
- Default Restyle Gradient — The default gradient intensity with the restyle color.
|
||||
- Subtle Black Gradient — A much less intense black gradient for contrast with the Quest assets.
|
||||
- No Gradient — Remove the gradient altogether. May cause the Quest assets to be difficult to see.
|
||||
- Asset Preload
|
||||
- Load All Quest Assets On Page Load — As soon as the Quest page is opened, load all Quest assets on the page.
|
||||
- Load Quest Assets During Page Scroll — Keep the default behavior of loading Quest assets as they come into view.
|
||||
5. Reorder Quests
|
||||
- Sort Quests by their status.
|
||||
- Comma-separated list must contain all of: `UNCLAIMED, CLAIMED, IGNORED, EXPIRED`.
|
||||
- Subsort status groups by Quest added date, expiring date, or claimed date.
|
||||
- Ignored Quest Profile
|
||||
- Share ignored quests between accounts on a single client or keep them separate.
|
||||
- Ignore Quests by opening their context menu and selecting `Mark as Ignored`.
|
||||
|
|
@ -430,7 +430,7 @@ function preprocessQuests(quests: Quest[]): Quest[] {
|
|||
|
||||
const unclaimedSortFunction = createSortFunction(unclaimedSubsort || "Recent DESC");
|
||||
|
||||
// Divide unclaimed quests by completion status before applying subsort.
|
||||
// Divide unclaimed Quests by completion status before applying subsort.
|
||||
questGroups.unclaimed.sort((a: Quest, b: Quest) => {
|
||||
const aCompleted = !!a.userStatus?.completedAt;
|
||||
const bCompleted = !!b.userStatus?.completedAt;
|
||||
|
|
@ -688,12 +688,12 @@ function processQuestForAutoComplete(quest: Quest): boolean {
|
|||
if (quest.userStatus?.completedAt || existingInterval) {
|
||||
return true;
|
||||
} else if (!playType && !watchType) {
|
||||
QuestifyLogger.warn(`[${getFormattedNow()}] Could not recognize the quest type for ${questName}.`);
|
||||
QuestifyLogger.warn(`[${getFormattedNow()}] Could not recognize the Quest type for ${questName}.`);
|
||||
return true;
|
||||
} else if ((watchType && !completeVideoQuestsInBackground) || (playType && (!completeGameQuestsInBackground || !IS_DISCORD_DESKTOP))) {
|
||||
return true;
|
||||
} else if (!questDuration) {
|
||||
QuestifyLogger.warn(`[${getFormattedNow()}] Could not find duration for quest ${questName}.`);
|
||||
QuestifyLogger.warn(`[${getFormattedNow()}] Could not find duration for Quest ${questName}.`);
|
||||
return true;
|
||||
} else if (watchType) {
|
||||
startVideoProgressTracking(quest, questDuration);
|
||||
|
|
@ -747,7 +747,7 @@ function getQuestAcceptedButtonText(quest: Quest): string | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
function getActiveQuestClosestToCompletion(): Quest | null {
|
||||
function getQuestPanelOverride(): Quest | null {
|
||||
let closestQuest: Quest | null = null;
|
||||
let closestTimeRemaining = Infinity;
|
||||
|
||||
|
|
@ -766,6 +766,23 @@ function getActiveQuestClosestToCompletion(): Quest | null {
|
|||
}
|
||||
});
|
||||
|
||||
if (!closestQuest) {
|
||||
const completedQuests = Array.from(QuestsStore.quests.values() as Quest[]).filter(q => q.userStatus?.completedAt).sort((a, b) => {
|
||||
const aTime = new Date(a.userStatus?.completedAt as string);
|
||||
const bTime = new Date(b.userStatus?.completedAt as string);
|
||||
return bTime.getTime() - aTime.getTime();
|
||||
});
|
||||
|
||||
completedQuests.forEach(quest => {
|
||||
const completedQuest = quest.userStatus?.completedAt;
|
||||
const questStatus = getQuestStatus(quest);
|
||||
|
||||
if (completedQuest && questStatus === QuestStatus.Unclaimed) {
|
||||
closestQuest = quest;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return closestQuest;
|
||||
}
|
||||
|
||||
|
|
@ -789,7 +806,7 @@ export default definePlugin({
|
|||
shouldHideGiftInventoryRelocationNotice,
|
||||
shouldHideFriendsListActiveNowPromotion,
|
||||
shouldDisableQuestAcceptedButton,
|
||||
getActiveQuestClosestToCompletion,
|
||||
getQuestPanelOverride,
|
||||
getQuestAcceptedButtonText,
|
||||
processQuestForAutoComplete,
|
||||
activeQuestIntervals,
|
||||
|
|
@ -858,7 +875,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Hides the new Quest popup above the account panel.
|
||||
// Allows in-progress quests to still show.
|
||||
// Allows in-progress Quests to still show.
|
||||
find: "QUESTS_BAR,questId",
|
||||
replacement: {
|
||||
match: /return null==(\i)\?null:\(/,
|
||||
|
|
@ -871,7 +888,7 @@ export default definePlugin({
|
|||
find: "questDeliveryOverride)?",
|
||||
replacement: {
|
||||
match: /(\i=)(\i.\i.questDeliveryOverride)/,
|
||||
replace: "$1$self.getActiveQuestClosestToCompletion()??$2"
|
||||
replace: "$1$self.getQuestPanelOverride()??$2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -957,7 +974,7 @@ export default definePlugin({
|
|||
group: true,
|
||||
replacement: [
|
||||
{
|
||||
// Restyles quest tiles with colors.
|
||||
// Restyles Quest tiles with colors.
|
||||
match: /className:(\i\(\)\(\i.container,\i\)),/,
|
||||
replace: "className:$self.getQuestTileClasses($1,arguments[0].quest),style:$self.getQuestTileStyle(arguments[0].quest),"
|
||||
},
|
||||
|
|
@ -974,15 +991,7 @@ export default definePlugin({
|
|||
]
|
||||
},
|
||||
{
|
||||
// Prevents default pinning of specific Quests to the top of the list.
|
||||
find: "questTile,{[",
|
||||
replacement: {
|
||||
match: /\i.unshift\(\i\):(\i.push\(\i\))/,
|
||||
replace: "$1:$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Sorts the "All Quests" tab quest tiles.
|
||||
// Sorts the "All Quests" tab Quest tiles.
|
||||
// Also sets mobile-only Quests as desktop compatible if the setting is enabled.
|
||||
find: ".ALL);return(",
|
||||
replacement: {
|
||||
|
|
@ -991,7 +1000,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
// Sorts the "Claimed Quests" tab quest tiles.
|
||||
// Sorts the "Claimed Quests" tab Quest tiles.
|
||||
find: ".ALL)}):(",
|
||||
replacement: {
|
||||
match: /(claimedQuests:(\i).{0,50}?;)/,
|
||||
|
|
@ -1025,7 +1034,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Open Video Modal
|
||||
match: /(\i\?)(\(0,\i.openVideoQuestModal\)\({quest:(\i))/,
|
||||
match: /(\i\?)?(\(0,\i.openVideoQuestModal\)\({quest:(\i))/,
|
||||
replace: "$1questifyContinue&&$2"
|
||||
},
|
||||
{
|
||||
|
|
@ -1054,7 +1063,7 @@ export default definePlugin({
|
|||
{
|
||||
// The Quest Accepted button is disabled by default. If the user reloads the client, they need a way
|
||||
// to resume the automatic completion, so patch in optionally enabling it if the feature is enabled.
|
||||
match: /(START_QUEST_CTA.{0,400}?)(!0)/,
|
||||
match: /(START_QUEST_CTA.{0,400}?disabled:)(!0)/,
|
||||
replace: "$1$self.shouldDisableQuestAcceptedButton(arguments[0].quest)??$2"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ export function fetchAndAlertQuests(source: string, logger: Logger): void {
|
|||
const shouldAlert = settings.store.fetchingQuestsAlert;
|
||||
|
||||
if (shouldAlert) {
|
||||
logger.info(`[${getFormattedNow()}] New quests detected. Playing alert sound.`);
|
||||
logger.info(`[${getFormattedNow()}] New Quests detected. Playing alert sound.`);
|
||||
AudioPlayer(shouldAlert, 1).play();
|
||||
} else {
|
||||
logger.info(`[${getFormattedNow()}] New quests detected.`);
|
||||
logger.info(`[${getFormattedNow()}] New Quests detected.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,13 +75,13 @@ export function startAutoFetchingQuests(seconds?: number): void {
|
|||
}
|
||||
|
||||
const interval = seconds ? seconds * 1000 : settings.store.fetchingQuestsInterval * 1000;
|
||||
QuestifyLogger.info(`[${getFormattedNow()}] Starting AutoFetch of quests every ${(interval / 60000).toFixed(2)} minutes.`);
|
||||
QuestifyLogger.info(`[${getFormattedNow()}] Starting AutoFetch of Quests every ${(interval / 60000).toFixed(2)} minutes.`);
|
||||
autoFetchInterval = setInterval(() => { fetchAndAlertQuests("Questify-AutoFetch", QuestifyLogger); }, interval);
|
||||
}
|
||||
|
||||
export function stopAutoFetchingQuests(): void {
|
||||
if (autoFetchInterval) {
|
||||
QuestifyLogger.info(`[${getFormattedNow()}] Stopping AutoFetch of quests.`);
|
||||
QuestifyLogger.info(`[${getFormattedNow()}] Stopping AutoFetch of Quests.`);
|
||||
clearInterval(autoFetchInterval);
|
||||
autoFetchInterval = null;
|
||||
}
|
||||
|
|
@ -712,8 +712,7 @@ function RestyleQuestsSetting() {
|
|||
restyleQuestsIgnored,
|
||||
restyleQuestsExpired,
|
||||
restyleQuestsGradient,
|
||||
restyleQuestsPreload,
|
||||
ignoredQuestProfile
|
||||
restyleQuestsPreload
|
||||
} = settings.use([
|
||||
"restyleQuestsUnclaimed",
|
||||
"restyleQuestsClaimed",
|
||||
|
|
@ -721,7 +720,6 @@ function RestyleQuestsSetting() {
|
|||
"restyleQuestsExpired",
|
||||
"restyleQuestsGradient",
|
||||
"restyleQuestsPreload",
|
||||
"ignoredQuestProfile"
|
||||
]);
|
||||
|
||||
const [unclaimedColor, setUnclaimedColor] = useState<number | null>(restyleQuestsUnclaimed);
|
||||
|
|
@ -871,26 +869,6 @@ function RestyleQuestsSetting() {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={q("main-inline-group")}>
|
||||
<div className={q("inline-group-item")}>
|
||||
<Forms.FormTitle className={q("form-subtitle")}>
|
||||
Ignored Quest Profile
|
||||
</Forms.FormTitle>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "Shared: All accounts on this client share ignores.", value: "shared" },
|
||||
{ label: "Private: All accounts on this client have separate ignores.", value: "private" }
|
||||
]}
|
||||
className={q("select")}
|
||||
popoutPosition="bottom"
|
||||
serialize={String}
|
||||
isSelected={(value: string) => value === ignoredQuestProfile}
|
||||
select={(value: string) => {
|
||||
settings.store.ignoredQuestProfile = value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={q("color-picker-container")}>
|
||||
{colorPickers.map(({ label, idx, defaultValue, value }) => (
|
||||
<div
|
||||
|
|
@ -942,12 +920,14 @@ function ReorderQuestsSetting(): JSX.Element {
|
|||
unclaimedSubsort,
|
||||
claimedSubsort,
|
||||
ignoredSubsort,
|
||||
expiredSubsort
|
||||
expiredSubsort,
|
||||
ignoredQuestProfile
|
||||
} = settings.use([
|
||||
"unclaimedSubsort",
|
||||
"claimedSubsort",
|
||||
"ignoredSubsort",
|
||||
"expiredSubsort"
|
||||
"expiredSubsort",
|
||||
"ignoredQuestProfile"
|
||||
]);
|
||||
|
||||
const getSubsortOptions = (source: string): SelectOption[] => {
|
||||
|
|
@ -1083,6 +1063,26 @@ function ReorderQuestsSetting(): JSX.Element {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={q("main-inline-group")}>
|
||||
<div className={q("inline-group-item")}>
|
||||
<Forms.FormTitle className={q("form-subtitle")}>
|
||||
Ignored Quest Profile
|
||||
</Forms.FormTitle>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "Shared: All accounts on this client share ignores.", value: "shared" },
|
||||
{ label: "Private: All accounts on this client have separate ignores.", value: "private" }
|
||||
]}
|
||||
className={q("select")}
|
||||
popoutPosition="bottom"
|
||||
serialize={String}
|
||||
isSelected={(value: string) => value === ignoredQuestProfile}
|
||||
select={(value: string) => {
|
||||
settings.store.ignoredQuestProfile = value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Forms.FormSection>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
|
@ -1314,7 +1314,7 @@ function FetchingQuestsSetting(): JSX.Element {
|
|||
<Forms.FormText className={q("form-description")}>
|
||||
Configure how often to fetch Quests from Discord and set up alerts for new Quests.
|
||||
<br /><br />
|
||||
By default, Discord only fetches quests on load and when visiting the Quests page.
|
||||
By default, Discord only fetches Quests on load and when visiting the Quests page.
|
||||
This means that without a fetch interval defined below, this plugin will become unaware
|
||||
of new Quests added throughout the day.
|
||||
<br /><br />
|
||||
|
|
@ -1609,25 +1609,25 @@ export const settings = definePluginSettings({
|
|||
},
|
||||
unclaimedSubsort: {
|
||||
type: OptionType.STRING,
|
||||
description: "Subsort method for unclaimed quests.",
|
||||
description: "Subsort method for unclaimed Quests.",
|
||||
default: "Expiring ASC", // "Recent ASC", "Recent DESC", "Expiring ASC", "Expiring DESC"
|
||||
hidden: true
|
||||
},
|
||||
claimedSubsort: {
|
||||
type: OptionType.STRING,
|
||||
description: "Subsort method for claimed quests.",
|
||||
description: "Subsort method for claimed Quests.",
|
||||
default: "Claimed DESC", // "Recent ASC", "Recent DESC", "Claimed ASC", "Claimed DESC"
|
||||
hidden: true
|
||||
},
|
||||
ignoredSubsort: {
|
||||
type: OptionType.STRING,
|
||||
description: "Subsort method for ignored quests.",
|
||||
description: "Subsort method for ignored Quests.",
|
||||
default: "Recent DESC", // "Recent ASC", "Recent DESC", "Expiring ASC", "Expiring DESC"
|
||||
hidden: true
|
||||
},
|
||||
expiredSubsort: {
|
||||
type: OptionType.STRING,
|
||||
description: "Subsort method for expired quests.",
|
||||
description: "Subsort method for expired Quests.",
|
||||
default: "Expiring DESC", // "Recent ASC", "Recent DESC", "Expiring ASC", "Expiring DESC"
|
||||
hidden: true
|
||||
},
|
||||
|
|
@ -1639,7 +1639,7 @@ export const settings = definePluginSettings({
|
|||
},
|
||||
onQuestsPage: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Whether the user is currently on the quests page.",
|
||||
description: "Whether the user is currently on the Quests page.",
|
||||
default: false,
|
||||
hidden: true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const QuestifyLogger = new Logger("Questify");
|
|||
export const PlayAudio = findByCodeLazy("Unable to find sound for pack name:");
|
||||
// Takes an audio name or URL, volume, and a callback function. Plays when play() is called.
|
||||
const AudioPlayerConstructor = findByCodeLazy("sound has no duration");
|
||||
export function AudioPlayer(name: string, volume: number = 1, callback?: () => void): any { return new AudioPlayerConstructor(name, null, volume, "default", callback || (() => { })); }
|
||||
export function AudioPlayer(name: string, volume: number = 1, callback?: () => void): any { return new AudioPlayerConstructor(name, null, volume, "default", callback ?? (() => { })); }
|
||||
export const QuestsStore = findStoreLazy("QuestsStore");
|
||||
export const questPath = "/discovery/quests";
|
||||
export const leftClick = 0;
|
||||
|
|
@ -267,7 +267,7 @@ export async function reportPlayGameQuestProgress(quest: Quest, terminal: boolea
|
|||
const progress = progressPlayType?.value || 1;
|
||||
|
||||
if (!questPlayType) {
|
||||
logger?.warn(`[${getFormattedNow()}] Could not recognize the quest type for Quest ${questName}.`);
|
||||
logger?.warn(`[${getFormattedNow()}] Could not recognize the Quest type for Quest ${questName}.`);
|
||||
return { progress: null };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default definePlugin({
|
|||
authors: [EquicordDevs.Jaxx],
|
||||
patches: [
|
||||
{
|
||||
find: '"SearchQueryStore";',
|
||||
find: '"SearchQueryStore");',
|
||||
replacement: {
|
||||
match: /\i\.searchResultsQuery=(\i)/,
|
||||
replace: "$&,$self.main($1)"
|
||||
|
|
|
|||
5
src/equicordplugins/sidebarChat/README.md
Normal file
5
src/equicordplugins/sidebarChat/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# SidebarChat
|
||||
|
||||
View another channel, dm or group dm as a resizable sidebar.
|
||||
|
||||

|
||||
5
src/equicordplugins/statusWhilePlaying.desktop/README.md
Normal file
5
src/equicordplugins/statusWhilePlaying.desktop/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# StatusWhilePlaying
|
||||
|
||||
This plugin automatically updates your online status (online, idle, dnd) when launching games.
|
||||
|
||||
It will change your status back to the prior status when the game is closed.
|
||||
8
src/equicordplugins/teX/README.md
Normal file
8
src/equicordplugins/teX/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# TeX
|
||||
|
||||
Renders TeX formulas.
|
||||
`` `$\sin x$` `` renders as $\sin x$,
|
||||
`` `$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$` `` renders as $\displaystyle x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$.
|
||||
The latter is intentionally set to an inline-block instead of a block, to save space.
|
||||
|
||||
This plugin requires external libraries, which will not work in the userscript version of Vencord without additional workarounds.
|
||||
3
src/equicordplugins/timelessClips.desktop/README.md
Normal file
3
src/equicordplugins/timelessClips.desktop/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# TimelessClips
|
||||
|
||||
Custom clip length
|
||||
3
src/equicordplugins/title/README.md
Normal file
3
src/equicordplugins/title/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Title
|
||||
|
||||
In the browser version, the window/tab title is always prefixed with "Discord -". This plugin lets you change or remove this prefix.
|
||||
7
src/equicordplugins/unreadBadgeCount/README.md
Normal file
7
src/equicordplugins/unreadBadgeCount/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# UnreadCountBadge
|
||||
|
||||
Show a badge in the channel list for unread messages.
|
||||
|
||||
you have to load the channel or get new messages in that channel for it show idk why maybe sometime ill figure it out
|
||||
|
||||

|
||||
3
src/equicordplugins/viewRawVariant/README.md
Normal file
3
src/equicordplugins/viewRawVariant/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ViewRawVariant
|
||||
|
||||
Improved ViewRaw with more features and places
|
||||
5
src/equicordplugins/voiceChatUtils/README.md
Normal file
5
src/equicordplugins/voiceChatUtils/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# VoiceChatUtilities
|
||||
|
||||
Allows you to perform multiple actions on an entire channel (move, mute, disconnect, etc.)
|
||||
|
||||

|
||||
7
src/equicordplugins/wallpaperFree/README.md
Normal file
7
src/equicordplugins/wallpaperFree/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# WallpaperFree
|
||||
|
||||
Recreation of the old DM wallpaper experiment; Set a background image for any channel, user or server.
|
||||
|
||||
<https://cdn.nest.rip/uploads/2e8a2928-8b10-458f-8036-362fab36633d.mp4>
|
||||
|
||||
*ignore the Discord wallpaper picker it is removed*
|
||||
5
src/equicordplugins/webpackTarball/README.md
Normal file
5
src/equicordplugins/webpackTarball/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# WebpackTarball
|
||||
|
||||
Adds an option to the Vencord Toolbox that lets you grab Discord's webpack as a tarball, which is very convenient for inspecting the code.
|
||||
|
||||
The reason it's a tarball instead of for example a zip is just simplicity — the tar file writer is only like 60ish lines.
|
||||
|
|
@ -33,8 +33,8 @@ export default definePlugin({
|
|||
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
|
||||
},
|
||||
{
|
||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
||||
replace: (m, notice) => `if(${notice}?.id=="EquicordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`
|
||||
match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/,
|
||||
replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue