Questify: Improve panel override patch and add ctx option to resume Auto Complete. (#698)

* Improve panel override, add ctx option.

* Improve panel text.
This commit is contained in:
Etorix 2026-01-28 05:57:00 -08:00 • committed by GitHub
parent 4cdac5b0d9
commit b4a0e9f6fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 76 additions and 47 deletions

View file

@ -245,12 +245,14 @@ function shouldHideMembersListActivelyPlayingIcon(): boolean {
return disableMembersListActivelyPlayingIcon || disableQuestsEverything;
}
function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any; }) {
function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any; }, isClaimedMenu: boolean = false): void {
const isIgnored = questIsIgnored(props.quest.id);
const isEnrolled = !!props.quest.userStatus?.enrolledAt;
const canAutoComplete = !isClaimedMenu && isEnrolled && canQuestAutoComplete(props.quest);
children.unshift((
<Menu.MenuGroup>
{!isIgnored ? (
{!isClaimedMenu && (!isIgnored ? (
<Menu.MenuItem
id={q("ignore-quests")}
label="Mark as Ignored"
@ -262,8 +264,8 @@ function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any;
label="Unmark as Ignored"
action={() => { removeIgnoredQuest(props.quest.id); }}
/>
)}
{activeQuestIntervals.has(props.quest.id) &&
))}
{activeQuestIntervals.has(props.quest.id) ? (
<Menu.MenuItem
id={q("stop-auto-complete")}
label="Stop Auto-Complete"
@ -277,7 +279,12 @@ function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any;
rerenderQuests();
}
}}
/>
/>) : canAutoComplete ? (
<Menu.MenuItem
id={q("start-auto-complete")}
label="Start Auto-Complete"
action={() => { processQuestForAutoComplete(props.quest); }}
/>) : null
}
<Menu.MenuItem
id={q("copy-quest-id")}
@ -858,9 +865,37 @@ async function startAchievementActivityProgressTracking(quest: Quest, target: {
}
}
function canQuestAutoComplete(quest: Quest): boolean {
const { completeVideoQuestsInBackground, completeGameQuestsInBackground, completeAchievementQuestsInBackground } = settings.store;
const task = getQuestTask(quest);
if (!task) { return false; }
const questStatus = getQuestStatus(quest);
const questCompleted = !!quest.userStatus?.completedAt;
if (questStatus !== QuestStatus.Unclaimed || questCompleted) {
return false;
}
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 watchTypeCompatible = isWatch && completeVideoQuestsInBackground;
const playTypeCompatible = isPlay && completeGameQuestsInBackground && IS_DISCORD_DESKTOP;
const achievementTypeCompatible = isAchievement && completeAchievementQuestsInBackground && IS_DISCORD_DESKTOP;
if (watchTypeCompatible || playTypeCompatible || achievementTypeCompatible) {
return true;
}
return false;
}
function processQuestForAutoComplete(quest: Quest): boolean {
const questName = normalizeQuestName(quest.config.messages.questName);
const { completeVideoQuestsInBackground, completeGameQuestsInBackground, completeAchievementQuestsInBackground } = settings.store;
const task = getQuestTask(quest);
const questTarget = getQuestTarget(task);
@ -869,17 +904,14 @@ function processQuestForAutoComplete(quest: Quest): boolean {
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);
const canAutoComplete = canQuestAutoComplete(quest);
if (quest.userStatus?.completedAt || existingInterval) {
return false;
} else if (!task) {
QuestifyLogger.warn(`[${getFormattedNow()}] Could not recognize the Quest type for ${questName}.`);
return false;
} else if (watchTypeIncompatible || playTypeIncompatible || achievementTypeIncompatible) {
} else if (!canAutoComplete) {
return false;
} else if (isWatch) {
startVideoProgressTracking(quest, questTarget);
@ -945,7 +977,7 @@ function getQuestAcceptedButtonText(quest: Quest, prepositional: boolean = false
if (((isPlay && completeGameQuestsInBackground && IS_DISCORD_DESKTOP) || (isWatch && completeVideoQuestsInBackground))) {
const { adjusted: durationWithLeeway } = getQuestTarget(task);
const currentProgress = getQuestProgress(quest, task) || 0;
const progress = Math.min((intervalData?.progress ?? currentProgress), durationWithLeeway);
const progress = Math.min(currentProgress, durationWithLeeway);
const timeRemaining = Math.max(0, durationWithLeeway - progress);
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")}`;
@ -993,31 +1025,27 @@ function getQuestPanelPercentComplete({ quest, percentCompleteText }: { quest: Q
}
function getQuestPanelSubtitleText(quest: Quest): string | null {
const questStatus = getQuestStatus(quest);
const questCompleted = !!quest.userStatus?.completedAt && questStatus === QuestStatus.Unclaimed;
const intervalData = activeQuestIntervals.get(quest.id);
const rewardItem = quest.config.rewardsConfig.rewards[0] || null;
const completingText = getQuestAcceptedButtonText(quest, true) || "Completing";
const completedText = questCompleted ? "Completed" : null;
if (!intervalData) {
if (!intervalData && !completedText) {
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}.`;
if (rewardItem?.orbQuantity) {
return `${completedText || completingText} for ${rewardItem.orbQuantity} Orbs.`;
} else if (rewardItem?.messages?.nameWithArticle) {
return `${completedText || completingText} for ${rewardItem.messages.nameWithArticle}.`;
} else {
return `${completingText} for an unrecognized reward.`;
return `${completedText || 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);
}
@ -1286,8 +1314,8 @@ export default definePlugin({
// be completed Quest which is actively being auto-completed.
find: "questDeliveryOverride)?",
replacement: {
match: /(\i=)(\i.\i.questDeliveryOverride)/,
replace: "$1$self.getQuestPanelOverride()??$2"
match: /(?<=null}\);return )(\i\?\i:\i)/,
replace: "$self.getQuestPanelOverride()??($1)"
}
},
{

View file

@ -558,7 +558,7 @@ function QuestButtonSettings(): JSX.Element {
</Heading>
<Select
options={questButtonClickOptions}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="top"
serialize={String}
isSelected={(value: string) => value === currentQuestButtonLeftClickAction}
@ -571,7 +571,7 @@ function QuestButtonSettings(): JSX.Element {
</Heading>
<Select
options={questButtonClickOptions}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="top"
serialize={String}
isSelected={(value: string) => value === currentQuestButtonMiddleClickAction}
@ -584,7 +584,7 @@ function QuestButtonSettings(): JSX.Element {
</Heading>
<Select
options={questButtonClickOptions}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="top"
serialize={String}
isSelected={(value: string) => value === currentQuestButtonRightClickAction}
@ -996,7 +996,7 @@ function RestyleQuestsSetting() {
</Heading>
<Select
options={gradientOptions}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="top"
serialize={String}
isSelected={(value: string) => value === restyleQuestsGradientValue}
@ -1009,7 +1009,7 @@ function RestyleQuestsSetting() {
</Heading>
<Select
options={preloadOptions}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="top"
serialize={String}
isSelected={(value: boolean) => value === restyleQuestsPreloadValue}
@ -1158,7 +1158,7 @@ function ReorderQuestsSetting(): JSX.Element {
</Heading>
<Select
options={getSubsortOptions("unclaimed")}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="bottom"
serialize={String}
isSelected={(value: string) => value === unclaimedSubsort}
@ -1173,7 +1173,7 @@ function ReorderQuestsSetting(): JSX.Element {
</Heading>
<Select
options={getSubsortOptions("claimed")}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="bottom"
serialize={String}
isSelected={(value: string) => value === claimedSubsort}
@ -1190,7 +1190,7 @@ function ReorderQuestsSetting(): JSX.Element {
</Heading>
<Select
options={getSubsortOptions("ignored")}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="bottom"
serialize={String}
isSelected={(value: string) => value === ignoredSubsort}
@ -1205,7 +1205,7 @@ function ReorderQuestsSetting(): JSX.Element {
</Heading>
<Select
options={getSubsortOptions("expired")}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="bottom"
serialize={String}
isSelected={(value: string) => value === expiredSubsort}
@ -1225,7 +1225,7 @@ function ReorderQuestsSetting(): JSX.Element {
{ 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")}
className={q("select", "titled-select")}
popoutPosition="bottom"
serialize={String}
isSelected={(value: string) => value === ignoredQuestProfile}
@ -1243,7 +1243,7 @@ function ReorderQuestsSetting(): JSX.Element {
{ label: "Yes", value: true },
{ label: "No", value: false }
]}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="bottom"
serialize={String}
isSelected={(value: boolean) => value === rememberQuestPageSort}
@ -1259,7 +1259,7 @@ function ReorderQuestsSetting(): JSX.Element {
{ label: "Yes", value: true },
{ label: "No", value: false }
]}
className={q("select")}
className={q("select", "titled-select")}
popoutPosition="bottom"
serialize={String}
isSelected={(value: boolean) => value === rememberQuestPageFilters}
@ -1521,7 +1521,7 @@ function FetchingQuestsSetting(): JSX.Element {
filter={(options, query) => options}
placeholder="Select or type an interval between 30 minutes and 12 hours."
feedback="Intervals must be between 30 minutes and 12 hours."
className={q("select")}
className={q("select", "titled-select")}
maxVisibleItems={resolvedIntervals.length + 1}
clearable={false}
multi={false}
@ -1553,7 +1553,7 @@ function FetchingQuestsSetting(): JSX.Element {
filter={(options, query) => options}
placeholder="Select a sound or provide a custom sound URL."
feedback="Sound not found, or URL is not from a supported domain."
className={q("select")}
className={q("select", "titled-select")}
clearable={true}
multi={false}
maxVisibleItems={7}

View file

@ -50,6 +50,11 @@
border-radius: 3px;
min-height: 43px;
border: 1px solid var(--border-normal);
margin-top: 8px;
}
.questify-titled-select > div {
margin-top: 0;
}
.questify-volume-slider-container {
@ -60,10 +65,6 @@
border: 1px solid var(--border-normal);
}
.questify-inline-group-item .questify-select {
margin-top: 0;
}
.questify-button,
.questify-sub-inline-group [class*="swatch"] {
height: 38px;