add custom status timeouts (#969)

* add custom status timeouts

patch the native status menu so idle, dnd, and invisible can use configurable timeout presets without replacing discord's ui

add a settings driven preset editor with reorder controls, movable forever support, and per status direct click defaults while keeping the preset list as the single source of truth and using discord's native timed status action

Signed-off-by: morsz <morsz@morsz.dev>

* clean up custom status timeouts

* framework

* Update index.tsx

---------

Signed-off-by: morsz <morsz@morsz.dev>
Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
Co-authored-by: creations <creations@creations.works>
This commit is contained in:
morsz 2026-04-12 06:30:47 +02:00 • committed by GitHub
parent 61341888d8
commit ad51504d6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 119 additions and 0 deletions

View file

@ -0,0 +1,115 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2026 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
const Millis = {
HALF_SECOND: 500,
SECOND: 1e3,
MINUTE: 6e4,
HOUR: 36e5,
DAY: 864e5,
WEEK: 6048e5,
DAYS_30: 2592e6
};
const settings = definePluginSettings({
showForeverOnTop: {
type: OptionType.BOOLEAN,
description: "Show the Forever option at the top of the list instead of the bottom.",
restartNeeded: true,
default: true
},
extraSeconds: {
type: OptionType.STRING,
description: "Extra seconds to add, separated by a comma (e.g. 5, 10, 30)",
restartNeeded: true,
default: "15, 30, 45"
},
extraMinutes: {
type: OptionType.STRING,
description: "Extra minutes to add, separated by a comma (e.g. 5, 10, 30)",
restartNeeded: true,
default: "5, 10, 30"
},
extraHours: {
type: OptionType.STRING,
description: "Extra hours to add, separated by a comma (e.g. 2, 4, 6, 12)",
restartNeeded: true,
default: "2, 4, 6, 12"
},
extraDays: {
type: OptionType.STRING,
description: "Extra days to add, separated by a comma (e.g. 1, 2)",
restartNeeded: true,
default: "1, 2"
},
});
export default definePlugin({
name: "CustomStatusTimeouts",
description: "Adds configurable timeout presets to the status (presence) menu.",
authors: [EquicordDevs.Kiri, EquicordDevs.thororen],
settings,
patches: [
{
find: "#{intl::DURATION_FOREVER}",
replacement: {
match: /\[\{duration.*?#{intl::DURATION_FOREVER}\)\}\]/,
replace: "$self.buildTimeouts($&)"
}
}
],
buildTimeouts(existing) {
const parse = (str: string) => str.split(",").map(s => Number(s.trim())).filter(Boolean);
const seconds = parse(settings.store.extraSeconds);
const minutes = parse(settings.store.extraMinutes);
const hours = parse(settings.store.extraHours);
const days = parse(settings.store.extraDays);
// I was testing weeks and months and for some reason random ones didnt apply look below for the working ones
// tested weeks between 1-12 working weeks were 1, 2, 3, 8, 9, 10 the rest didnt work
// tested months between 1-12 working months were 2, 4, 10, 12 the rest didnt work
// for months I only tested evens except for 1 so I hardcoded them to avoid user complaints
const weeks = [1, 2, 3];
const months = [2, 4];
const extra = [
...seconds.map(s => ({
duration: s * Millis.SECOND,
label: () => `For ${s} ${s === 1 ? "Second" : "Seconds"}`
})),
...minutes.map(m => ({
duration: m * Millis.MINUTE,
label: () => `For ${m} ${m === 1 ? "Minute" : "Minutes"}`
})),
...hours.map(h => ({
duration: h * Millis.HOUR,
label: () => `For ${h} ${h === 1 ? "Hour" : "Hours"}`
})),
...days.map(d => ({
duration: d * Millis.DAY,
label: () => `For ${d} ${d === 1 ? "Day" : "Days"}`
})),
...weeks.map(w => ({
duration: w * Millis.WEEK,
label: () => `For ${w} ${w === 1 ? "Week" : "Weeks"}`
})),
...months.map(m => ({
duration: m * Millis.DAYS_30,
label: () => `For ${m} ${m === 1 ? "Month" : "Months"}`
})),
];
return [...existing, ...extra].sort((a, b) => {
if (a.duration === undefined) return settings.store.showForeverOnTop ? -1 : 1;
if (b.duration === undefined) return settings.store.showForeverOnTop ? 1 : -1;
return a.duration - b.duration;
});
}
});

View file

@ -1284,6 +1284,10 @@ export const EquicordDevs = Object.freeze({
name: "Ape",
id: 722832640360841308n,
},
Kiri: {
name: "Kiri",
id: 310525496771346434n,
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly