Fix patches for Questify, SilentTyping, SMYN on Canary. (#670)

* Questify

* SilentTyping

* ShowMeYourName
This commit is contained in:
Etorix 2026-01-23 09:02:51 -08:00 • committed by GitHub
parent f4bef87da1
commit 56eab42950
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 231 additions and 106 deletions

View file

@ -19,8 +19,8 @@ import { ContextMenuApi, Menu, NavigationRouter, RestAPI, useEffect, useState }
import { JSX } from "react";
import { addIgnoredQuest, addRerenderCallback, autoFetchCompatible, fetchAndAlertQuests, maximumAutoFetchIntervalValue, minimumAutoFetchIntervalValue, questIsIgnored, removeIgnoredQuest, rerenderQuests, settings, startAutoFetchingQuests, stopAutoFetchingQuests, validateAndOverwriteIgnoredQuests } from "./settings";
import { ExcludedQuestMap, GuildlessServerListItem, Quest, QuestIcon, QuestMap, QuestStatus, RGB } from "./utils/components";
import { adjustRGB, decimalToRGB, fetchAndDispatchQuests, formatLowerBadge, getFormattedNow, getIgnoredQuestIDs, getQuestStatus, isDarkish, leftClick, middleClick, normalizeQuestName, q, QuestifyLogger, questPath, QuestsStore, refreshQuest, reportPlayGameQuestProgress, reportVideoQuestProgress, rightClick, setIgnoredQuestIDs, videoQuestLeeway, waitUntilEnrolled } from "./utils/misc";
import { ExcludedQuestMap, GuildlessServerListItem, Quest, QuestIcon, QuestMap, QuestStatus, QuestTaskType, RGB } from "./utils/components";
import { adjustRGB, decimalToRGB, fetchAndDispatchQuests, formatLowerBadge, getFormattedNow, getIgnoredQuestIDs, getQuestProgress, getQuestStatus, getQuestTarget, getQuestTask, isDarkish, leftClick, middleClick, normalizeQuestName, q, QuestifyLogger, questPath, QuestsStore, refreshQuest, reportPlayGameQuestProgress, reportVideoQuestProgress, rightClick, setIgnoredQuestIDs, videoQuestLeeway, waitUntilEnrolled } from "./utils/misc";
const QuestifyNative = VencordNative.pluginHelpers.Questify as PluginNative<typeof import("./native")>;
const patchedMobileQuests = new Set<string>();
@ -206,8 +206,7 @@ function shouldHideBadgeOnUserProfiles(): boolean {
function shouldHideQuestPopup(quest: Quest | null): boolean {
const {
disableQuestsPopupAboveAccountPanel,
disableQuestsEverything,
triggerQuestsRerender
disableQuestsEverything
} = settings.use([
"disableQuestsPopupAboveAccountPanel",
"disableQuestsEverything",
@ -291,8 +290,6 @@ function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any;
export function getQuestTileClasses(originalClasses: string, quest: Quest, color: number | null | undefined, gradient: string | undefined): string {
const {
ignoredQuestIDs,
ignoredQuestProfile,
restyleQuestsUnclaimed,
restyleQuestsClaimed,
restyleQuestsIgnored,
@ -366,7 +363,7 @@ export function getQuestTileClasses(originalClasses: string, quest: Quest, color
}
function makeDesktopCompatible(quests: Quest[]): void {
const { makeMobileQuestsDesktopCompatible, triggerQuestsRerender } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]);
const { makeMobileQuestsDesktopCompatible } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]);
if (makeMobileQuestsDesktopCompatible) {
quests.forEach(quest => {
@ -378,7 +375,7 @@ function makeDesktopCompatible(quests: Quest[]): void {
tasks.WATCH_VIDEO = {
...tasks.WATCH_VIDEO_ON_MOBILE,
type: "WATCH_VIDEO"
type: QuestTaskType.WATCH_VIDEO
};
}
});
@ -398,17 +395,11 @@ function makeDesktopCompatible(quests: Quest[]): void {
function sortQuests(quests: Quest[], skip?: boolean): Quest[] {
const {
ignoredQuestIDs,
ignoredQuestProfile,
reorderQuests,
unclaimedSubsort,
claimedSubsort,
ignoredSubsort,
expiredSubsort,
completeVideoQuestsInBackground,
completeGameQuestsInBackground,
completeAchievementQuestsInBackground,
triggerQuestsRerender
} = settings.use([
"ignoredQuestIDs",
"ignoredQuestProfile",
@ -510,11 +501,7 @@ function sortQuests(quests: Quest[], skip?: boolean): Quest[] {
}
export function getQuestTileStyle(quest: Quest | null): Record<string, string> {
const {
restyleQuests,
ignoredQuestIDs,
ignoredQuestProfile
} = settings.use([
settings.use([
"restyleQuests",
"ignoredQuestIDs",
"ignoredQuestProfile"
@ -684,8 +671,8 @@ async function startVideoProgressTracking(quest: Quest, questTarget: number): Pr
async function startPlayGameProgressTracking(quest: Quest, questTarget: number): Promise<void> {
const questName = normalizeQuestName(quest.config.messages.questName);
const questEnrolledAt = quest.userStatus?.enrolledAt ? new Date(quest.userStatus.enrolledAt) : null;
const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY;
const initialProgress = quest.userStatus?.progress?.[playType?.type || ""]?.value || 0;
const task = getQuestTask(quest);
const initialProgress = getQuestProgress(quest, task) || 0;
const remaining = Math.max(0, questTarget - initialProgress);
const heartbeatInterval = 20; // Heartbeats must be at most 2 minutes apart.
activeQuestIntervals.set(quest.id, { progressTimeout: null as any, rerenderTimeout: null as any, progress: initialProgress, type: "play" });
@ -872,19 +859,23 @@ async function startAchievementActivityProgressTracking(quest: Quest, questTarge
function processQuestForAutoComplete(quest: Quest): boolean {
const questName = normalizeQuestName(quest.config.messages.questName);
const { completeVideoQuestsInBackground, completeGameQuestsInBackground, completeAchievementQuestsInBackground, makeMobileQuestsDesktopCompatible } = settings.store;
const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY;
const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO;
const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY;
const questTarget = playType?.target || watchType?.target || achievementType?.target || 0;
const { completeVideoQuestsInBackground, completeGameQuestsInBackground, completeAchievementQuestsInBackground } = settings.store;
const task = getQuestTask(quest);
const questTarget = getQuestTarget(task);
const existingInterval = activeQuestIntervals.get(quest.id);
const watchTypeIncompatible = watchType && !completeVideoQuestsInBackground;
const playTypeIncompatible = playType && (!completeGameQuestsInBackground || !IS_DISCORD_DESKTOP);
const achievementTypeIncompatible = achievementType && (!completeAchievementQuestsInBackground || !IS_DISCORD_DESKTOP);
const isWatch = task?.type === QuestTaskType.WATCH_VIDEO || task?.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE;
const isPlay = task?.type === QuestTaskType.PLAY_ON_DESKTOP || task?.type === QuestTaskType.PLAY_ON_XBOX || task?.type === QuestTaskType.PLAY_ON_PLAYSTATION || task?.type === QuestTaskType.PLAY_ACTIVITY;
const isAchievement = task?.type === QuestTaskType.ACHIEVEMENT_IN_ACTIVITY;
const watchTypeIncompatible = isWatch && !completeVideoQuestsInBackground;
const playTypeIncompatible = isPlay && (!completeGameQuestsInBackground || !IS_DISCORD_DESKTOP);
const achievementTypeIncompatible = isAchievement && (!completeAchievementQuestsInBackground || !IS_DISCORD_DESKTOP);
if (quest.userStatus?.completedAt || existingInterval) {
return false;
} else if (!playType && !watchType && !achievementType) {
} else if (!task) {
QuestifyLogger.warn(`[${getFormattedNow()}] Could not recognize the Quest type for ${questName}.`);
return false;
} else if (watchTypeIncompatible || playTypeIncompatible || achievementTypeIncompatible) {
@ -892,13 +883,13 @@ function processQuestForAutoComplete(quest: Quest): boolean {
} else if (!questTarget) {
QuestifyLogger.warn(`[${getFormattedNow()}] Could not find target for Quest ${questName}.`);
return false;
} else if (watchType) {
} else if (isWatch) {
startVideoProgressTracking(quest, questTarget);
return true;
} else if (playType) {
} else if (isPlay) {
startPlayGameProgressTracking(quest, questTarget);
return true;
} else if (achievementType) {
} else if (isAchievement) {
startAchievementActivityProgressTracking(quest, questTarget);
return true;
}
@ -921,17 +912,20 @@ function shouldDisableQuestAcceptedButton(quest: Quest): boolean | null {
}
function getQuestUnacceptedButtonText(quest: Quest): string | null {
const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground, makeMobileQuestsDesktopCompatible } = settings.store;
const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY;
const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO;
const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY;
const target = (playType?.target || (watchType?.target ? watchType.target - videoQuestLeeway : undefined) || achievementType?.target || 0);
const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground } = settings.store;
const task = getQuestTask(quest);
const target = getQuestTarget(task);
const targetFormatted = `${String(Math.floor(target / 60)).padStart(2, "0")}:${String(target % 60).padStart(2, "0")}`;
const isWatch = task?.type === QuestTaskType.WATCH_VIDEO || task?.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE;
const isPlay = task?.type === QuestTaskType.PLAY_ON_DESKTOP || task?.type === QuestTaskType.PLAY_ON_XBOX || task?.type === QuestTaskType.PLAY_ON_PLAYSTATION || task?.type === QuestTaskType.PLAY_ACTIVITY;
const isAchievement = task?.type === QuestTaskType.ACHIEVEMENT_IN_ACTIVITY;
if (target > 0) {
if ((playType && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (watchType && completeVideoQuestsInBackground)) {
if ((isPlay && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (isWatch && completeVideoQuestsInBackground)) {
return `Complete (${targetFormatted})`;
} else if (achievementType && completeAchievementQuestsInBackground) {
} else if (isAchievement && completeAchievementQuestsInBackground) {
return "Complete (Immediate)";
}
}
@ -939,30 +933,36 @@ function getQuestUnacceptedButtonText(quest: Quest): string | null {
return null;
}
function getQuestAcceptedButtonText(quest: Quest): string | null {
const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground, makeMobileQuestsDesktopCompatible } = settings.store;
function getQuestAcceptedButtonText(quest: Quest, prepositional: boolean = false): string | null {
const { completeGameQuestsInBackground, completeVideoQuestsInBackground, completeAchievementQuestsInBackground } = settings.store;
const questEnrolledAt = quest.userStatus?.enrolledAt ? new Date(quest.userStatus.enrolledAt) : null;
const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY;
const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO;
const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY;
const task = getQuestTask(quest);
const intervalData = activeQuestIntervals.get(quest.id);
const isWatch = task?.type === QuestTaskType.WATCH_VIDEO || task?.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE;
const isPlay = task?.type === QuestTaskType.PLAY_ON_DESKTOP || task?.type === QuestTaskType.PLAY_ON_XBOX || task?.type === QuestTaskType.PLAY_ON_PLAYSTATION || task?.type === QuestTaskType.PLAY_ACTIVITY;
const isAchievement = task?.type === QuestTaskType.ACHIEVEMENT_IN_ACTIVITY;
if (questEnrolledAt) {
if (((playType && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (watchType && completeVideoQuestsInBackground))) {
const taskType = playType || watchType;
const duration = taskType?.target || 0;
const durationWithLeeway = watchType ? duration - videoQuestLeeway : duration;
const progress = Math.min((intervalData?.progress ?? (quest.userStatus?.progress?.[taskType?.type || ""]?.value || 0)), durationWithLeeway);
if (((isPlay && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (isWatch && completeVideoQuestsInBackground))) {
const durationWithLeeway = getQuestTarget(task);
const currentProgress = getQuestProgress(quest, task) || 0;
const progress = Math.min((intervalData?.progress ?? currentProgress), durationWithLeeway);
const timeRemaining = Math.max(0, durationWithLeeway - progress);
const canCompleteImmediately = watchType && questEnrolledAt && ((new Date().getTime() - questEnrolledAt.getTime()) / 1000) >= durationWithLeeway;
const canCompleteImmediately = isWatch && questEnrolledAt && ((new Date().getTime() - questEnrolledAt.getTime()) / 1000) >= durationWithLeeway;
const progressFormatted = `${String(Math.floor(timeRemaining / 60)).padStart(2, "0")}:${String(timeRemaining % 60).padStart(2, "0")}`;
const progressFormattedAsPreposition = timeRemaining >= 60
? `${Math.floor(timeRemaining / 60)}m ${timeRemaining % 60}s`
: `${timeRemaining % 60}s`;
if (!!intervalData) {
return !canCompleteImmediately && timeRemaining ? `Completing (${progressFormatted})` : "Completing...";
} else if (watchType || playType) {
return canCompleteImmediately || !timeRemaining
? "Completing..."
: `Completing ${prepositional ? `in ${progressFormattedAsPreposition}` : `(${progressFormatted})`}`;
} else if (isWatch || isPlay) {
return canCompleteImmediately ? "Complete (Immediate)" : timeRemaining === durationWithLeeway ? `Complete (${progressFormatted})` : `Resume (${progressFormatted})`;
}
} else if (achievementType && completeAchievementQuestsInBackground) {
} else if (isAchievement && completeAchievementQuestsInBackground) {
if (!!intervalData) {
return "Completing...";
} else {
@ -974,8 +974,56 @@ function getQuestAcceptedButtonText(quest: Quest): string | null {
return null;
}
function getQuestPanelPercentComplete({ quest, percentCompleteText }: { quest: Quest; percentCompleteText?: string; }): { percentComplete: number; } | { percentComplete: number; percentCompleteText: string; } | null {
const task = getQuestTask(quest);
if (!task) { return null; }
const questTarget = getQuestTarget(task);
const questProgress = getQuestProgress(quest, task);
if (!questTarget || questProgress === null) { return null; }
const decimal = Math.min(1, questProgress / questTarget);
return percentCompleteText ? {
percentComplete: decimal,
percentCompleteText: `${Math.floor(decimal * 100)}%`
} : {
percentComplete: decimal
};
}
function getQuestPanelSubtitleText(quest: Quest): string | null {
const intervalData = activeQuestIntervals.get(quest.id);
if (!intervalData) {
return null;
}
const rewardItem = quest.config.rewardsConfig.rewards[0] || null;
const completingText = getQuestAcceptedButtonText(quest, true) || "Completing";
if (rewardItem.orbQuantity) {
return `${completingText} for ${rewardItem.orbQuantity} Orbs.`;
} else if (rewardItem.messages?.nameWithArticle) {
return `${completingText} for ${rewardItem.messages.nameWithArticle}.`;
} else {
return `${completingText} for an unrecognized reward.`;
}
}
function getQuestPanelTitleText(quest: Quest): string | null {
const intervalData = activeQuestIntervals.get(quest.id);
if (!intervalData) {
return null;
}
return normalizeQuestName(quest.config.messages.questName);
}
function getQuestPanelOverride(): Quest | null {
const { makeMobileQuestsDesktopCompatible } = settings.store;
let closestQuest: Quest | null = null;
let closestTimeRemaining = Infinity;
@ -986,10 +1034,8 @@ function getQuestPanelOverride(): Quest | null {
return;
}
const playType = quest.config.taskConfigV2?.tasks.PLAY_ON_DESKTOP || quest.config.taskConfigV2?.tasks.PLAY_ON_XBOX || quest.config.taskConfigV2?.tasks.PLAY_ON_PLAYSTATION || quest.config.taskConfigV2?.tasks.PLAY_ACTIVITY;
const watchType = (makeMobileQuestsDesktopCompatible && quest.config.taskConfigV2?.tasks.WATCH_VIDEO_ON_MOBILE) || quest.config.taskConfigV2?.tasks.WATCH_VIDEO;
const achievementType = quest.config.taskConfigV2?.tasks.ACHIEVEMENT_IN_ACTIVITY;
const duration = playType?.target || (watchType?.target ? watchType.target - videoQuestLeeway : undefined) || achievementType?.target || 0;
const task = getQuestTask(quest);
const duration = getQuestTarget(task);
if (!duration) {
return;
@ -1037,7 +1083,7 @@ function disguiseHomeButton(location: string): boolean {
}
function useQuestRerender(): number {
const { triggerQuestsRerender } = settings.use(["triggerQuestsRerender"]);
settings.use(["triggerQuestsRerender"]);
const [renderTrigger, setRenderTrigger] = useState(0);
useEffect(() => addRerenderCallback(() => setRenderTrigger(prev => prev + 1)), []);
return renderTrigger;
@ -1064,17 +1110,17 @@ function setLastFilterChoices(filters: { group: string; filter: string; }[]): vo
function getQuestAcceptedButtonProps(quest: Quest, text: string, disabled: boolean, onClick?: () => void) {
const validTasks = [
"WATCH_VIDEO",
"WATCH_VIDEO_ON_MOBILE",
"PLAY_ON_DESKTOP",
"PLAY_ON_XBOX",
"PLAY_ON_PLAYSTATION",
"PLAY_ACTIVITY",
"ACHIEVEMENT_IN_ACTIVITY"
QuestTaskType.WATCH_VIDEO,
QuestTaskType.WATCH_VIDEO_ON_MOBILE,
QuestTaskType.PLAY_ON_DESKTOP,
QuestTaskType.PLAY_ON_XBOX,
QuestTaskType.PLAY_ON_PLAYSTATION,
QuestTaskType.PLAY_ACTIVITY,
QuestTaskType.ACHIEVEMENT_IN_ACTIVITY
];
const validTask = Array.from(validTasks).some(taskType => Object.values(quest.config.taskConfigV2?.tasks || {}).some(
task => task.type === taskType && (task.type !== "WATCH_VIDEO_ON_MOBILE" || settings.store.makeMobileQuestsDesktopCompatible)
task => task.type === taskType && (task.type !== QuestTaskType.WATCH_VIDEO_ON_MOBILE || settings.store.makeMobileQuestsDesktopCompatible)
));
if (!validTask) {
@ -1121,9 +1167,12 @@ export default definePlugin({
shouldHideFriendsListActiveNowPromotion,
shouldHideMembersListActivelyPlayingIcon,
getQuestUnacceptedButtonText,
getQuestPanelPercentComplete,
processQuestForAutoComplete,
getQuestAcceptedButtonProps,
getQuestAcceptedButtonText,
getQuestPanelSubtitleText,
getQuestPanelTitleText,
getQuestPanelOverride,
setLastFilterChoices,
getLastFilterChoices,
@ -1138,8 +1187,8 @@ export default definePlugin({
// Hides the notice in the gift inventory that Quests have been relocated to the Discovery tab.
find: "quests-wumpus-hikes-mountain-transparent-background",
replacement: {
match: /return(\(0,\i.\i\)\("div",{className:)/,
replace: "return $self.shouldHideGiftInventoryRelocationNotice()?null:$1"
match: /return(?=\(0,\i.\i\)\("div",{className:)/,
replace: "return $self.shouldHideGiftInventoryRelocationNotice()?null:"
}
},
{
@ -1217,6 +1266,23 @@ export default definePlugin({
replace: "return !$self.shouldHideQuestPopup($1)&&("
}
},
{
// Fixes the progress tracking for auto-completing Quests.
find: ",{progressTextAnimation:",
replacement: {
match: /(?<=children:\i}=)(\i)/,
replace: "Object.assign({},$1,$self.getQuestPanelPercentComplete($1))"
}
},
{
// Overrides the title and subtitle to provide more
// useful information for Quests being auto-completed.
find: '"progress-title"',
replacement: {
match: /(?<=return.{0,150}?quest:(\i),percentComplete:\i.{0,280}?"progress-title",children.{0,115}?children:)(\i.{0,50}"progress-subtitle",isTextTransition:!0,children.{0,115}?children:)/,
replace: "$self.getQuestPanelTitleText($1)??$2$self.getQuestPanelSubtitleText($1)??"
}
},
{
// Replaces the default displayed Quest with the soonest to
// be completed Quest which is actively being auto-completed.
@ -1265,8 +1331,8 @@ export default definePlugin({
replacement: [
{
// Extracts the custom dropdown prop before the variable is overwritten.
match: /(\((\i),\i\){)(var{options:\i,)/,
replace: "$1const vcDynamicDropdownFeedback=$2.feedback;$3"
match: /(?<=forwardRef\(function\((\i),\i\){)/,
replace: "const vcDynamicDropdownFeedback=$1.feedback;"
},
{
// Passes the custom prop to the dropdown's invalid handler.
@ -1287,7 +1353,7 @@ export default definePlugin({
replacement: [
{
// Extracts the custom maxDigits prop.
match: /(=>{var{count:\i,)/,
match: /(=>{let{count:\i,)/,
replace: "$1maxDigits,"
},
{
@ -1310,8 +1376,8 @@ export default definePlugin({
replacement: [
{
// 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),"
match: /(?<=\i.current=\i},className:)(\i\(\)\(\i.\i,\i\)),/,
replace: "$self.getQuestTileClasses($1,arguments[0].quest),style:$self.getQuestTileStyle(arguments[0].quest),"
},
{
// Encourages banners to load quicker if the setting is enabled.
@ -1383,7 +1449,7 @@ export default definePlugin({
{
// Loads the last used sort method and filter choices.
// Defaults to sorting by "Questify" and no filters.
find: "headingControls,children:",
find: "({resetSortingFiltering:()",
group: true,
replacement: [
{
@ -1417,7 +1483,7 @@ export default definePlugin({
// Whether preloading assets is enabled or not, the placeholders loading
// before the assets causes a lot of element shifting, whereas if
// the elements load immediately instead, it doesn't.
find: "rewardDescriptionContainer,children",
find: ",{rewardWithArticleHook:()",
replacement: {
match: /showPlaceholder:!\i/,
replace: "showPlaceholder:false"
@ -1430,7 +1496,7 @@ export default definePlugin({
replacement: [
{
// Resume Video Quest.
match: /(onClick:\(\)=>)(\(0,\i.openVideoQuestModal\)\({quest:(\i))/,
match: /(onClick:\(\)=>)(\(0,\i.\i\)\({quest:(\i))/,
replace: "$1!$self.processQuestForAutoComplete($3)&&$2"
},
{
@ -1473,7 +1539,7 @@ export default definePlugin({
{
// Sets intervals to progress Play Game Quests in the background.
// Triggers if a Quest has already been started but was interrupted, such as by a reload.
find: "platformSelectorPrimary,",
find: "WATCH_VIDEO,skipEnrollmentCheck:!0})",
group: true,
replacement: [
{

View file

@ -364,7 +364,6 @@ function validateQuestButtonSetting() {
function validateDisableQuestSetting() {
const {
disableQuestsEverything,
disableQuestsDiscoveryTab,
disableQuestsFetchingQuests,
disableQuestsDirectMessagesTab,

View file

@ -72,6 +72,21 @@ export interface ExcludedQuest {
id: string;
}
export enum QuestTaskType {
WATCH_VIDEO = "WATCH_VIDEO",
WATCH_VIDEO_ON_MOBILE = "WATCH_VIDEO_ON_MOBILE",
PLAY_ON_DESKTOP = "PLAY_ON_DESKTOP",
PLAY_ON_XBOX = "PLAY_ON_XBOX",
PLAY_ON_PLAYSTATION = "PLAY_ON_PLAYSTATION",
PLAY_ACTIVITY = "PLAY_ACTIVITY",
ACHIEVEMENT_IN_ACTIVITY = "ACHIEVEMENT_IN_ACTIVITY"
}
export interface QuestTask {
type: QuestTaskType;
target: number;
}
export interface Quest {
questifyNumber: number;
id: string;
@ -88,36 +103,40 @@ export interface Quest {
rewardsConfig: {
rewards: {
type: QuestRewardType;
orbQuantity?: number;
messages?: {
nameWithArticle: string;
};
}[];
},
taskConfigV2: {
tasks: {
WATCH_VIDEO?: {
type: "WATCH_VIDEO";
type: QuestTaskType.WATCH_VIDEO;
target: number;
},
WATCH_VIDEO_ON_MOBILE?: {
type: "WATCH_VIDEO_ON_MOBILE";
type: QuestTaskType.WATCH_VIDEO_ON_MOBILE;
target: number;
},
PLAY_ON_DESKTOP?: {
type: "PLAY_ON_DESKTOP";
type: QuestTaskType.PLAY_ON_DESKTOP;
target: number;
},
PLAY_ON_XBOX?: {
type: "PLAY_ON_XBOX";
type: QuestTaskType.PLAY_ON_XBOX;
target: number;
},
PLAY_ON_PLAYSTATION?: {
type: "PLAY_ON_PLAYSTATION";
type: QuestTaskType.PLAY_ON_PLAYSTATION;
target: number;
},
PLAY_ACTIVITY?: {
type: "PLAY_ACTIVITY";
type: QuestTaskType.PLAY_ACTIVITY;
target: number;
};
ACHIEVEMENT_IN_ACTIVITY?: {
type: "ACHIEVEMENT_IN_ACTIVITY";
type: QuestTaskType.ACHIEVEMENT_IN_ACTIVITY;
target: number;
eventName: "progress";
applications: {
@ -134,27 +153,27 @@ export interface Quest {
enrolledAt: string | null;
progress: null | {
WATCH_VIDEO?: {
eventName: "WATCH_VIDEO";
eventName: QuestTaskType.WATCH_VIDEO;
value: number;
},
WATCH_VIDEO_ON_MOBILE?: {
eventName: "WATCH_VIDEO_ON_MOBILE";
eventName: QuestTaskType.WATCH_VIDEO_ON_MOBILE;
value: number;
},
PLAY_ON_DESKTOP?: {
eventName: "PLAY_ON_DESKTOP";
eventName: QuestTaskType.PLAY_ON_DESKTOP;
value: number;
},
PLAY_ON_XBOX?: {
eventName: "PLAY_ON_XBOX";
eventName: QuestTaskType.PLAY_ON_XBOX;
value: number;
},
PLAY_ON_PLAYSTATION?: {
eventName: "PLAY_ON_PLAYSTATION";
eventName: QuestTaskType.PLAY_ON_PLAYSTATION;
value: number;
},
PLAY_ACTIVITY?: {
eventName: "PLAY_ACTIVITY";
eventName: QuestTaskType.PLAY_ACTIVITY;
value: number;
},
};

View file

@ -4,13 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { questIsIgnored, settings } from "@equicordplugins/questify/settings";
import { classNameFactory } from "@utils/css";
import { Logger } from "@utils/Logger";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, RestAPI, UserStore } from "@webpack/common";
import { Quest, QuestStatus, RGB } from "./components";
import { activeQuestIntervals } from "..";
import { questIsIgnored, settings } from "../settings";
import { Quest, QuestStatus, QuestTask, QuestTaskType, RGB } from "./components";
export const q = classNameFactory("questify-");
export const QuestifyLogger = new Logger("Questify");
@ -38,6 +39,46 @@ export function getIgnoredQuestIDs(userId?: string): string[] {
return ignoredQuestIDs[key];
}
export function getQuestTask(quest: Quest): QuestTask {
const tasks = quest.config.taskConfigV2?.tasks;
return (tasks.PLAY_ON_DESKTOP ||
tasks.PLAY_ON_XBOX ||
tasks.PLAY_ON_PLAYSTATION ||
tasks.PLAY_ACTIVITY ||
tasks.WATCH_VIDEO ||
tasks.WATCH_VIDEO_ON_MOBILE ||
tasks.ACHIEVEMENT_IN_ACTIVITY) as QuestTask;
}
export function getQuestProgress(quest: Quest, task: QuestTask) {
const intervalData = activeQuestIntervals.get(quest.id);
if (intervalData) { return intervalData.progress; }
const progressMap = quest.userStatus?.progress;
if (!progressMap) { return null; }
if (task.type === QuestTaskType.WATCH_VIDEO || task.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE) {
const watchProgress = progressMap.WATCH_VIDEO?.value;
const mobileProgress = progressMap.WATCH_VIDEO_ON_MOBILE?.value;
if (watchProgress !== undefined || mobileProgress !== undefined) {
return Math.max(watchProgress ?? 0, mobileProgress ?? 0);
}
return null;
}
return progressMap[task.type]?.value ?? null;
}
export function getQuestTarget(task: QuestTask) {
const isWatch = task.type === QuestTaskType.WATCH_VIDEO || task.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE;
return task.target - (isWatch ? videoQuestLeeway : 0);
}
export function getQuestStatus(quest: Quest, checkIgnored: boolean = true): QuestStatus {
const completedQuest = quest.userStatus?.completedAt;
const claimedQuest = quest.userStatus?.claimedAt;

View file

@ -959,7 +959,7 @@ export default definePlugin({
},
{
// Replace names in profile popouts.
find: "clickableUsername,\"aria-label\":",
find: "shouldWrap:!0,loop:!0,inProfile:!0",
replacement: {
match: /(tags:\i,)nickname:(\i)/,
replace: "$1showMeYourNameNickname:$2=$self.getMemberListProfilesReactionsVoiceNameText({...arguments[0],type:\"profilesPopout\"})??(arguments[0].nickname)"
@ -968,7 +968,7 @@ export default definePlugin({
{
// Replace names in the profile tooltip for switching between guild and global profiles.
// You must open a profile modal before the code this is patching will be searchable.
find: "\"view-main-profile\",",
find: 'id:"view-server-profile",',
group: true,
replacement: [
{
@ -990,18 +990,18 @@ export default definePlugin({
}
},
{
find: "discriminator,forceUsername",
find: "MESSAGE,userId:",
group: true,
replacement: [
{
// Replace names in reaction popouts.
match: /(?<=className:\i.reactorInfo,children:)(\[.{0,450}?}\)\])/,
replace: "($self.getMemberListProfilesReactionsVoiceNameElement({user:arguments[0].user,guildId:arguments[0].guildId,type:\"reactionsPopout\"}))??($1)"
// Track hovering over reaction popouts.
match: /(?<=return\(0,\i.\i\)\(\i.\i,{className:\i.\i,)(?=onContextMenu:\i=>)/,
replace: "onMouseEnter:()=>{$self.addHoveringReactionPopout(arguments[0].user.id)},onMouseLeave:()=>{$self.removeHoveringReactionPopout(arguments[0].user.id)},$2"
},
{
// Track hovering over reaction popouts.
match: /(return\(0,\i.\i\)\(\i.\i,{className:\i.reactor,)(onContextMenu)/,
replace: "$1onMouseEnter:()=>{$self.addHoveringReactionPopout(arguments[0].user.id)},onMouseLeave:()=>{$self.removeHoveringReactionPopout(arguments[0].user.id)},$2"
// Replace names in reaction popouts.
match: /(?<=Child,{className:\i.\i,children:)/,
replace: "($self.getMemberListProfilesReactionsVoiceNameElement({user:arguments[0].user,guildId:arguments[0].guildId,type:\"reactionsPopout\"}))??"
}
]
},

View file

@ -328,8 +328,8 @@ export default definePlugin({
replace: "const silentTypingShouldHideChatBarTypingIndicators=$self.shouldHideChatBarTypingIndicators();$1"
},
{
match: /(typingDots,ref:\i,children:)(\[.{0,340}?}\)\])/,
replace: "$1silentTypingShouldHideChatBarTypingIndicators?[]:$2"
match: /("stop-animation".{0,80}?ref:\i,children:)/,
replace: "$1silentTypingShouldHideChatBarTypingIndicators?[]:"
}
]
},