update(timezones) (#273)
Some checks are pending
Test / Test (push) Waiting to run

* move to a list on load instead of each reqest, move refresh and set database to toolbox

* add option to hide your own timezone from showing

* fix auth cancel, add fist run toast, fix label, add default to sys timezone when showing if nothing at all is set

* Update index.tsx
This commit is contained in:
Creation's 2025-05-29 19:30:17 -04:00 committed by GitHub
parent aa63557c11
commit 64a429cfbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 110 additions and 96 deletions

View file

@ -24,19 +24,17 @@ export function SetTimezoneModal({ userId, modalProps, database }: { userId: str
const [currentValue, setCurrentValue] = useState<string | null>(timezones[userId] ?? null); const [currentValue, setCurrentValue] = useState<string | null>(timezones[userId] ?? null);
useEffect(() => { useEffect(() => {
if (!database) return;
const localTimezone = timezones[userId]; const localTimezone = timezones[userId];
const shouldUseDatabase = const shouldUseDatabase =
settings.store.useDatabase && settings.store.useDatabase &&
(settings.store.preferDatabaseOverLocal || !localTimezone); (settings.store.preferDatabaseOverLocal || !localTimezone);
if (shouldUseDatabase) { const value = shouldUseDatabase
getTimezone(userId).then(e => setCurrentValue(e ?? localTimezone)); ? getTimezone(userId) ?? localTimezone
} else { : localTimezone;
setCurrentValue(localTimezone);
} setCurrentValue(value ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
}, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal, database]); }, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal]);
const options = useMemo(() => { const options = useMemo(() => {
return Intl.supportedValuesOf("timeZone").map(timezone => { return Intl.supportedValuesOf("timeZone").map(timezone => {

View file

@ -4,55 +4,45 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { DataStore } from "@api/index"; import { openModal } from "@utils/index";
import { openModal } from "@utils/modal";
import { OAuth2AuthorizeModal, showToast, Toasts } from "@webpack/common"; import { OAuth2AuthorizeModal, showToast, Toasts } from "@webpack/common";
import { databaseTimezones } from "."; const databaseTimezones: Record<string, { value: string | null; }> = {};
export const DOMAIN = "https://timezone.creations.works"; const DOMAIN = "https://timezone.creations.works";
export const REDIRECT_URI = `${DOMAIN}/auth/discord/callback`; const REDIRECT_URI = `${DOMAIN}/auth/discord/callback`;
export const CLIENT_ID = "1377021506810417173"; const CLIENT_ID = "1377021506810417173";
export const DATASTORE_KEY = "vencord-database-timezones";
const pendingRequests: Record<string, Promise<string | null>> = {};
export async function setUserDatabaseTimezone(userId: string, timezone: string | null) { export async function setUserDatabaseTimezone(userId: string, timezone: string | null) {
databaseTimezones[userId] = { databaseTimezones[userId] = { value: timezone };
value: timezone,
expires: Date.now() + 60 * 60 * 1000 // 1 hour
};
await DataStore.set(DATASTORE_KEY, databaseTimezones);
} }
export async function getTimezone(userId: string, force?: boolean): Promise<string | null> { export function getTimezone(userId: string): string | null {
const now = Date.now(); return databaseTimezones[userId]?.value ?? null;
}
const cached = databaseTimezones[userId]; export async function loadDatabaseTimezones(): Promise<boolean> {
if (cached && now < cached.expires && !force) return cached.value; try {
const res = await fetch(`${DOMAIN}/list`, {
headers: { Accept: "application/json" }
});
if (!pendingRequests[userId]) { if (res.ok) {
pendingRequests[userId] = (async () => { const json = await res.json();
const res = await fetch(`${DOMAIN}/get?id=${userId}`, { for (const id in json) {
headers: { Accept: "application/json" } databaseTimezones[id] = {
}); value: json[id]?.timezone ?? null
};
let value: string | null = null;
if (res.ok) {
const json = await res.json();
if (json?.timezone && typeof json.timezone === "string") {
value = json.timezone;
}
} }
setUserDatabaseTimezone(userId, value); return true;
delete pendingRequests[userId]; }
return value;
})();
}
return pendingRequests[userId]; return false;
} catch (e) {
console.error("Failed to fetch timezones list:", e);
return false;
}
} }
export async function setTimezone(timezone: string): Promise<boolean> { export async function setTimezone(timezone: string): Promise<boolean> {
@ -92,6 +82,8 @@ export function authModal(callback?: () => void) {
permissions={0n} permissions={0n}
cancelCompletesFlow={false} cancelCompletesFlow={false}
callback={async (res: any) => { callback={async (res: any) => {
if (!res || !res.location) return;
try { try {
const url = new URL(res.location); const url = new URL(res.location);
@ -109,10 +101,10 @@ export function authModal(callback?: () => void) {
showToast("Authorization successful!", Toasts.Type.SUCCESS); showToast("Authorization successful!", Toasts.Type.SUCCESS);
callback?.(); callback?.();
} catch (e) { } catch (e) {
console.error("Error during authorization:", e);
showToast("Unexpected error during authorization", Toasts.Type.FAILURE); showToast("Unexpected error during authorization", Toasts.Type.FAILURE);
} }
}} }}
/> />
)); ));
} }

View file

@ -1,6 +1,6 @@
/* /*
* Vencord, a Discord client mod * Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors * Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
@ -17,15 +17,9 @@ import { findByPropsLazy } from "@webpack";
import { Button, Menu, showToast, Toasts, Tooltip, useEffect, UserStore, useState } from "@webpack/common"; import { Button, Menu, showToast, Toasts, Tooltip, useEffect, UserStore, useState } from "@webpack/common";
import { Message, User } from "discord-types/general"; import { Message, User } from "discord-types/general";
import { authModal, deleteTimezone, getTimezone, setUserDatabaseTimezone } from "./database"; import { authModal, deleteTimezone, getTimezone, loadDatabaseTimezones, setUserDatabaseTimezone } from "./database";
import { SetTimezoneModal } from "./TimezoneModal"; import { SetTimezoneModal } from "./TimezoneModal";
type CacheEntry = {
value: string | null;
expires: number;
};
export let databaseTimezones: Record<string, CacheEntry> = {};
export let timezones: Record<string, string | null> = {}; export let timezones: Record<string, string | null> = {};
export const DATASTORE_KEY = "vencord-timezones"; export const DATASTORE_KEY = "vencord-timezones";
@ -33,6 +27,12 @@ const classes = findByPropsLazy("timestamp", "compact", "contentOnly");
const locale = findByPropsLazy("getLocale"); const locale = findByPropsLazy("getLocale");
export const settings = definePluginSettings({ export const settings = definePluginSettings({
"Show Own Timezone": {
type: OptionType.BOOLEAN,
description: "Show your own timezone in profiles and message headers",
default: true
},
"24h Time": { "24h Time": {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show time in 24h format", description: "Show time in 24h format",
@ -70,8 +70,7 @@ export const settings = definePluginSettings({
<Button onClick={() => { <Button onClick={() => {
authModal(async () => { authModal(async () => {
openModal(modalProps => <SetTimezoneModal userId={UserStore.getCurrentUser().id} modalProps={modalProps} database={true} />); openModal(modalProps => <SetTimezoneModal userId={UserStore.getCurrentUser().id} modalProps={modalProps} database={true} />);
} });
);
}}> }}>
Set Timezone on Database Set Timezone on Database
</Button> </Button>
@ -94,6 +93,13 @@ export const settings = definePluginSettings({
Reset Database Timezones Reset Database Timezones
</Button> </Button>
) )
},
askedTimezone: {
type: OptionType.BOOLEAN,
description: "Whether the user has been asked to set their timezone",
hidden: true,
default: false
} }
}); });
@ -112,6 +118,7 @@ interface Props {
timestamp?: string; timestamp?: string;
type: "message" | "profile"; type: "message" | "profile";
} }
const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Props) => { const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Props) => {
const [currentTime, setCurrentTime] = useState(timestamp || Date.now()); const [currentTime, setCurrentTime] = useState(timestamp || Date.now());
const [timezone, setTimezone] = useState<string | null>(null); const [timezone, setTimezone] = useState<string | null>(null);
@ -123,7 +130,7 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop
(settings.store.preferDatabaseOverLocal || !localTimezone); (settings.store.preferDatabaseOverLocal || !localTimezone);
if (shouldUseDatabase) { if (shouldUseDatabase) {
getTimezone(userId).then(e => setTimezone(e ?? localTimezone)); setTimezone(getTimezone(userId) ?? localTimezone);
} else { } else {
setTimezone(localTimezone); setTimezone(localTimezone);
} }
@ -178,45 +185,18 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop
); );
}, { noop: true }); }, { noop: true });
const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User; }) => { const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User; }) => {
if (user?.id == null) return; if (user?.id == null) return;
const setTimezoneItem = ( const setTimezoneItem = (
<Menu.MenuItem <Menu.MenuItem
label="Set Timezone" label="Set Local Timezone"
id="set-timezone" id="set-timezone"
action={() => openModal(modalProps => <SetTimezoneModal userId={user.id} modalProps={modalProps} />)} action={() => openModal(modalProps => <SetTimezoneModal userId={user.id} modalProps={modalProps} />)}
/> />
); );
children.push(<Menu.MenuSeparator />, setTimezoneItem); children.push(<Menu.MenuSeparator />, setTimezoneItem);
if (settings.store.useDatabase) {
const refreshTimezoneItem = (
<Menu.MenuItem
label="Refresh Timezone"
id="refresh-timezone"
action={async () => {
showToast("Refreshing timezone...", Toasts.Type.CLOCK);
try {
const timezone = await getTimezone(user.id, true);
if (timezone) {
showToast("Timezone refreshed successfully!", Toasts.Type.SUCCESS);
} else {
showToast("Timezone reset successfully!", Toasts.Type.SUCCESS);
}
} catch (error) {
console.error("Failed to refresh timezone:", error);
showToast("Failed to refresh timezone.", Toasts.Type.FAILURE);
}
}}
/>
);
children.push(refreshTimezoneItem);
}
}; };
export default definePlugin({ export default definePlugin({
@ -246,31 +226,75 @@ export default definePlugin({
} }
], ],
toolboxActions: {
"Set Database Timezone": () => {
authModal(async () => {
openModal(modalProps => <SetTimezoneModal userId={UserStore.getCurrentUser().id} modalProps={modalProps} database={true} />);
});
},
"Refresh Database Timezones": async () => {
try {
const good = await loadDatabaseTimezones();
if (good) {
showToast("Timezones refreshed successfully!", Toasts.Type.SUCCESS);
} else {
showToast("Timezones Failed to refresh!", Toasts.Type.FAILURE);
}
}
catch (error) {
console.error("Failed to refresh timezone:", error);
showToast("Failed to refresh timezones.", Toasts.Type.FAILURE);
}
}
},
async start() { async start() {
databaseTimezones = await DataStore.get<Record<string, CacheEntry>>(DATASTORE_KEY) || {};
timezones = await DataStore.get<Record<string, string>>(DATASTORE_KEY) || {}; timezones = await DataStore.get<Record<string, string>>(DATASTORE_KEY) || {};
if (settings.store.useDatabase) {
await loadDatabaseTimezones();
if (!settings.store.askedTimezone) {
showToast(
"",
Toasts.Type.MESSAGE,
{
duration: 10000,
component: (
<Button
color={Button.Colors.GREEN}
onClick={() => {
authModal(async () => {
openModal(modalProps => <SetTimezoneModal userId={UserStore.getCurrentUser().id} modalProps={modalProps} database={true} />);
});
}}
>
Want to save your timezone to the database? Click here to set it.
</Button>
),
position: Toasts.Position.BOTTOM
}
);
settings.store.askedTimezone = true;
}
}
}, },
settings, settings,
getTime, getTime,
renderProfileTimezone: (props?: { user?: User; }) => { renderProfileTimezone: (props?: { user?: User; }) => {
if (!settings.store.showProfileTime || !props?.user?.id) return null; if (!settings.store.showProfileTime || !props?.user?.id) return null;
if (props.user.id === UserStore.getCurrentUser().id && !settings.store["Show Own Timezone"]) return null;
return <TimestampComponent return <TimestampComponent userId={props.user.id} type="profile" />;
userId={props.user.id}
type="profile"
/>;
}, },
renderMessageTimezone: (props?: { message?: Message; }) => { renderMessageTimezone: (props?: { message?: Message; }) => {
if (!settings.store.showMessageHeaderTime || !props?.message) return null; if (!settings.store.showMessageHeaderTime || !props?.message) return null;
if (props.message.author.id === UserStore.getCurrentUser().id && !settings.store["Show Own Timezone"]) return null;
return <TimestampComponent return <TimestampComponent userId={props.message.author.id} timestamp={props.message.timestamp.toISOString()} type="message" />;
userId={props.message.author.id}
timestamp={props.message.timestamp.toISOString()}
type="message"
/>;
} }
}); });