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;
}
}