diff --git a/src/equicordplugins/questify/components/questFeaturesSetting.tsx b/src/equicordplugins/questify/components/questFeaturesSetting.tsx index 05a6a1a8d..48c1c06e1 100644 --- a/src/equicordplugins/questify/components/questFeaturesSetting.tsx +++ b/src/equicordplugins/questify/components/questFeaturesSetting.tsx @@ -257,15 +257,15 @@ export function QuestFeaturesSetting(): JSX.Element { Modify Features c !== undefined)}> - Discord has implemented a Quest Ban system which will temporarily or permanently limit your access to completing Quests and claiming their rewards if you are found to be completing them through unofficial means. Modifying the completion of Quests is against their Terms of Service. + Discord is known to punish users of scripts or plugins that modify the completion of Quests. Modifying the completion of Quests is against Discord's Terms of Service.
- The punishment appears limited to loss of access to Quests and their rewards, but Discord may escalate at any time. + The punishment consists of a temporary or permanent loss of access to Quests and their rewards. The punishment also consists of an account standing violation which lasts 2 years per violation.
- Due to the various methods Discord uses to track users, there's no way to realistically evade detection. If you proceed, understand that Discord likely will detect it at some point. + Due to the various methods Discord uses to track users, there's no way to realistically evade detection. If you proceed, understand that Discord likely will detect your use at some point.
diff --git a/src/equicordplugins/questify/settings/notices.tsx b/src/equicordplugins/questify/settings/notices.tsx index daebfec03..ee2026eea 100644 --- a/src/equicordplugins/questify/settings/notices.tsx +++ b/src/equicordplugins/questify/settings/notices.tsx @@ -4,17 +4,29 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import type { ModalActionVariant, RenderModalProps } from "@vencord/discord-types"; -import { Modal, openModal } from "@webpack/common"; -import type { JSX } from "react"; +import type { ModalOptions, ModalProps, RenderModalProps } from "@vencord/discord-types"; +import { Modal, openModal, useEffect, useState } from "@webpack/common"; +import type { ComponentType, JSX } from "react"; import { getQuestifySettings } from "./access"; import { resetDangerousSettings } from "./dangerous"; import { promptToRestartIfDirty } from "./restartTracking"; -interface NoticeAction { +type NoticeActionVariant = "primary" | "secondary" | "critical-primary" | "critical-secondary" | "active" | "overlay-primary" | "overlay-secondary" | "expressive"; + +type QuestifyModalOptions = ModalOptions & { dismissable?: boolean; }; +type QuestifyModalProps = ModalProps & { dismissable?: boolean; }; + +const QuestifyModal = Modal as ComponentType; + +interface NoticeActionStep { text: string; - variant?: ModalActionVariant; + variant?: NoticeActionVariant; + disabledFor?: number; // Seconds + confirmation?: NoticeActionStep; +} + +interface NoticeAction extends NoticeActionStep { run?: () => void; promptForRestart?: boolean; } @@ -22,6 +34,7 @@ interface NoticeAction { interface OneTimeNotice { id: string; title: string; + dismissable?: boolean; renderBody: () => JSX.Element; condition: () => boolean; autoAcknowledgeCondition?: () => boolean; @@ -31,7 +44,7 @@ interface OneTimeNotice { const oneTimeNotices = [ { id: "quest-ban-warning-2026-08-07", - title: "Questify Notice", + title: "Questify Notice - August 7th, 2026", renderBody: () =>

Discord has implemented a Quest Ban system which will temporarily or permanently limit your access to completing Quests and claiming their rewards if you are found to be completing them through unofficial means. Modifying the completion of Quests is against their Terms of Service. @@ -58,7 +71,49 @@ const oneTimeNotices = [ }, { text: "Disable Dangerous Questify Settings", - variant: "secondary", + variant: "primary", + run: resetDangerousSettings, + promptForRestart: true, + }, + ], + }, + { + id: "quest-ban-warning-2026-08-26", + title: "Questify Notice - August 26th, 2026", + dismissable: false, + renderBody: () =>

+

+ Discord has begun punishing users of scripts and plugins that modify the completion of Quests. Modifying the completion of Quests is against their Terms of Service. +

+
+

+ The punishment consists of a temporary or permanent loss of access to Quests and their rewards. The punishment also consists of an account standing violation which lasts 2 years per violation. +

+
+

+ Due to the various methods Discord uses to track users, there's no way to realistically evade detection. If you proceed, understand that Discord likely will detect your use at some point. +

+
, + condition: () => { + const settings = getQuestifySettings(); + + return settings.enabled && settings.allowChangingDangerousSettings; + }, + autoAcknowledgeCondition: () => !getQuestifySettings().allowChangingDangerousSettings, + actions: [ + { + text: "Keep Using Dangerous Questify Settings", + variant: "critical-primary", + disabledFor: 10, + confirmation: { + text: "Are You Sure?", + variant: "critical-secondary", + disabledFor: 5, + }, + }, + { + text: "Disable Dangerous Questify Settings", + variant: "primary", run: resetDangerousSettings, promptForRestart: true, }, @@ -87,21 +142,76 @@ function runNoticeAction(notice: OneTimeNotice, action: NoticeAction, onClose: ( }, 0); } +interface NoticeActionState { + step: NoticeActionStep; + startedAt: number; +} + +function getRemainingSeconds({ startedAt, step }: NoticeActionState, now: number): number { + const disabledFor = Math.max(0, step.disabledFor ?? 0); + + return Math.max(0, Math.ceil((startedAt + disabledFor * 1000 - now) / 1000)); +} + +function isNoticeDismissable(notice: OneTimeNotice): boolean { + return notice.dismissable ?? true; +} + function OneTimeNoticeModal({ notice, ...modalProps }: RenderModalProps & { notice: OneTimeNotice; }): JSX.Element { + const [actionStates, setActionStates] = useState(() => { + const startedAt = Date.now(); + + return notice.actions.map(step => ({ step, startedAt })); + }); + + const [now, setNow] = useState(Date.now); + const remainingSeconds = actionStates.map(actionState => getRemainingSeconds(actionState, now)); + const isCountingDown = remainingSeconds.some(seconds => seconds > 0); + + useEffect(() => { + if (!isCountingDown) return; + + const timeout = setTimeout(() => setNow(Date.now()), 250); + + return () => clearTimeout(timeout); + }, [actionStates, isCountingDown, now]); + + function handleActionClick(actionIndex: number): void { + const { confirmation } = actionStates[actionIndex].step; + + if (confirmation) { + const startedAt = Date.now(); + + setActionStates(currentStates => currentStates.map((actionState, index) => ( + index === actionIndex + ? { step: confirmation, startedAt } + : actionState + ))); + setNow(startedAt); + return; + } + + runNoticeAction(notice, notice.actions[actionIndex], modalProps.onClose); + } + return ( - ({ - text: action.text, - variant: action.variant ?? "primary", - onClick: () => runNoticeAction(notice, action, modalProps.onClose), + actions={actionStates.map(({ step }, actionIndex) => ({ + text: remainingSeconds[actionIndex] > 0 + ? `${step.text} (${remainingSeconds[actionIndex]})` + : step.text, + variant: step.variant ?? "primary", + disabled: remainingSeconds[actionIndex] > 0, + onClick: () => handleActionClick(actionIndex), }))} > {notice.renderBody()} - + ); } @@ -119,7 +229,11 @@ export function showPendingQuestifyNotice(): void { continue; } - openModal(modalProps => ); + const modalOptions: QuestifyModalOptions = { + dismissable: isNoticeDismissable(notice), + }; + + openModal(modalProps => , modalOptions); return; } }