mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-23 23:15:10 +00:00
separate components/remove api backend setting/wip oauth
This commit is contained in:
parent
964801da3a
commit
1fd506da73
7 changed files with 210 additions and 150 deletions
|
@ -7,15 +7,12 @@
|
|||
import { DataStore } from "@api/index";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { showToast, Toasts, UserStore } from "@webpack/common";
|
||||
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common";
|
||||
|
||||
import { ReviewDBAuth } from "./entities";
|
||||
|
||||
const DATA_STORE_KEY = "rdb-auth";
|
||||
|
||||
const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
|
||||
|
||||
export let Auth: ReviewDBAuth = {};
|
||||
|
||||
export async function initAuth() {
|
||||
|
|
|
@ -6,33 +6,33 @@
|
|||
|
||||
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import settings from "./settings";
|
||||
|
||||
export const DEFAULT_API = "https://timezonedb.catvibers.me/api";
|
||||
export const API_URL: string = "https://timezonedb.catvibers.me/api";
|
||||
|
||||
export type Snowflake = string;
|
||||
type ApiError = { error: string; };
|
||||
type UserFetchResponse = ApiError | { timezoneId: string }
|
||||
type BulkFetchResponse = ApiError | Record<Snowflake, { timezoneId: string | null }>;
|
||||
|
||||
export async function verifyApi(url: string): Promise<boolean> {
|
||||
if (url === DEFAULT_API) return true;
|
||||
|
||||
const res = await fetch(url, {
|
||||
export async function verifyLogin(token: string): Promise<boolean> {
|
||||
const res = await fetch(API_URL, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"User-Agent": VENCORD_USER_AGENT,
|
||||
"Authorization": token,
|
||||
},
|
||||
});
|
||||
|
||||
return "logged_in" in await res.json();
|
||||
const json: { logged_in?: boolean } = await res.json();
|
||||
return !!json.logged_in;
|
||||
}
|
||||
|
||||
export async function fetchTimezonesBulk(ids: Snowflake[]): Promise<Record<Snowflake, string | null> | undefined> {
|
||||
try {
|
||||
const { apiUrl } = settings.store;
|
||||
const req = await fetch(`${apiUrl}/user/bulk`, {
|
||||
const req = await fetch(`${API_URL}/user/bulk`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
@ -58,8 +58,7 @@ export async function fetchTimezonesBulk(ids: Snowflake[]): Promise<Record<Snowf
|
|||
|
||||
export async function fetchTimezone(userId: Snowflake): Promise<string | null | undefined> {
|
||||
try {
|
||||
const { apiUrl } = settings.store;
|
||||
const req = await fetch(`${apiUrl}/user/${userId}`, {
|
||||
const req = await fetch(`${API_URL}/user/${userId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"User-Agent": VENCORD_USER_AGENT,
|
||||
|
@ -80,3 +79,10 @@ export async function fetchTimezone(userId: Snowflake): Promise<string | null |
|
|||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentToken(): string | undefined {
|
||||
const userId = UserStore.getCurrentUser().id;
|
||||
const { tokens } = settings.store;
|
||||
|
||||
return tokens[userId];
|
||||
}
|
||||
|
|
95
src/plugins/timezones/components/LocalTimestamp.tsx
Normal file
95
src/plugins/timezones/components/LocalTimestamp.tsx
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "../styles.css";
|
||||
|
||||
import { ErrorBoundary } from "@components/index";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { React, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { Snowflake } from "../api";
|
||||
import { getUserTimezone } from "../cache";
|
||||
import { formatTimestamp } from "../utils";
|
||||
import { openTimezoneOverrideModal } from "./SetTimezoneOverrideModal";
|
||||
|
||||
// Based on Syncxv's vc-timezones user plugin //
|
||||
|
||||
const messageClasses = findByPropsLazy("timestamp", "compact", "contentOnly");
|
||||
|
||||
interface LocalTimestampProps {
|
||||
userId: Snowflake;
|
||||
timestamp?: Date;
|
||||
type: "message" | "profile";
|
||||
}
|
||||
|
||||
export function LocalTimestamp(props: LocalTimestampProps): JSX.Element {
|
||||
return <ErrorBoundary noop={true} wrappedProps={props}>
|
||||
<LocalTimestampInner {...props} />
|
||||
</ErrorBoundary>;
|
||||
}
|
||||
|
||||
function LocalTimestampInner(props: LocalTimestampProps): JSX.Element | null {
|
||||
const [timezone, setTimezone] = useState<string | null>();
|
||||
const [timestamp, setTimestamp] = useState(props.timestamp ?? Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (!timezone) {
|
||||
getUserTimezone(props.userId, props.type === "profile").then(setTimezone);
|
||||
return;
|
||||
}
|
||||
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
if (props.type === "profile") {
|
||||
setTimestamp(Date.now());
|
||||
|
||||
const now = new Date();
|
||||
const delay = (60 - now.getSeconds()) * 1000 + 1000 - now.getMilliseconds();
|
||||
|
||||
timer = setTimeout(() => setTimestamp(Date.now()), delay);
|
||||
}
|
||||
|
||||
return () => timer && clearTimeout(timer);
|
||||
}, [timezone, timestamp]);
|
||||
|
||||
if (!timezone) return null;
|
||||
|
||||
const longTime = formatTimestamp(timezone, timestamp, true);
|
||||
const shortTime = formatTimestamp(timezone, timestamp, false);
|
||||
|
||||
if (props.type === "message" && !shortTime)
|
||||
return null;
|
||||
|
||||
const shortTimeFormatted = props.type === "message"
|
||||
? `• ${shortTime}`
|
||||
: shortTime ?? "Error";
|
||||
const classes = props.type === "message"
|
||||
? `vc-timezones-message-display ${messageClasses.timestamp}`
|
||||
: "vc-timezones-profile-display";
|
||||
|
||||
return <Tooltip
|
||||
position="top"
|
||||
// @ts-ignore
|
||||
delay={750}
|
||||
allowOverflow={false}
|
||||
spacing={8}
|
||||
hideOnClick={true}
|
||||
tooltipClassName="vc-timezones-tooltip"
|
||||
hide={!longTime}
|
||||
text={longTime}
|
||||
>
|
||||
{toolTipProps => <>
|
||||
<span {...toolTipProps}
|
||||
className={classes}
|
||||
onClick={() => {
|
||||
toolTipProps.onClick();
|
||||
openTimezoneOverrideModal(props.userId);
|
||||
}}>
|
||||
{shortTimeFormatted}
|
||||
</span>
|
||||
</>}
|
||||
</Tooltip>;
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
import "../styles.css";
|
||||
|
||||
import { ErrorBoundary, Link } from "@components/index";
|
||||
import { Margins } from "@utils/margins";
|
||||
|
@ -18,102 +18,27 @@ import {
|
|||
ModalRoot,
|
||||
openModal,
|
||||
} from "@utils/modal";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, Forms, React, SearchableSelect, Text, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
import { Button, Forms, React, SearchableSelect, Text, useEffect, useState } from "@webpack/common";
|
||||
import { SelectOption } from "@webpack/types";
|
||||
|
||||
import { Snowflake } from "./api";
|
||||
import { getUserTimezone } from "./cache";
|
||||
import settings, { TimezoneOverrides } from "./settings";
|
||||
import { formatTimestamp, getTimezonesLazy } from "./utils";
|
||||
import settings, { TimezoneOverrides } from "../settings";
|
||||
import { getTimezonesLazy } from "../utils";
|
||||
import { openTimezoneDBAuthModal } from "./TimezoneDBAuthModal";
|
||||
|
||||
// Based on Syncxv's vc-timezones user plugin //
|
||||
|
||||
const messageClasses = findByPropsLazy("timestamp", "compact", "contentOnly");
|
||||
|
||||
interface LocalTimestampProps {
|
||||
userId: Snowflake;
|
||||
timestamp?: Date;
|
||||
type: "message" | "profile";
|
||||
export function openTimezoneOverrideModal(userId: string) {
|
||||
openModal(modalProps => <>
|
||||
<ErrorBoundary>
|
||||
<SetTimezoneOverrideModal userId={userId} modalProps={modalProps} />
|
||||
</ErrorBoundary>
|
||||
</>);
|
||||
}
|
||||
|
||||
export function LocalTimestamp(props: LocalTimestampProps): JSX.Element {
|
||||
return <ErrorBoundary noop={true} wrappedProps={props}>
|
||||
<LocalTimestampInner {...props} />
|
||||
</ErrorBoundary>;
|
||||
}
|
||||
|
||||
function LocalTimestampInner(props: LocalTimestampProps): JSX.Element | null {
|
||||
const [timezone, setTimezone] = useState<string | null>();
|
||||
const [timestamp, setTimestamp] = useState(props.timestamp ?? Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (!timezone) {
|
||||
getUserTimezone(props.userId, props.type === "profile").then(setTimezone);
|
||||
return;
|
||||
}
|
||||
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
if (props.type === "profile") {
|
||||
setTimestamp(Date.now());
|
||||
|
||||
const now = new Date();
|
||||
const delay = (60 - now.getSeconds()) * 1000 + 1000 - now.getMilliseconds();
|
||||
|
||||
timer = setTimeout(() => setTimestamp(Date.now()), delay);
|
||||
}
|
||||
|
||||
return () => timer && clearTimeout(timer);
|
||||
}, [timezone, timestamp]);
|
||||
|
||||
if (!timezone) return null;
|
||||
|
||||
const longTime = formatTimestamp(timezone, timestamp, true);
|
||||
const shortTime = formatTimestamp(timezone, timestamp, false);
|
||||
|
||||
if (props.type === "message" && !shortTime)
|
||||
return null;
|
||||
|
||||
const shortTimeFormatted = props.type === "message"
|
||||
? `• ${shortTime}`
|
||||
: shortTime ?? "Error";
|
||||
const classes = props.type === "message"
|
||||
? `vc-timezones-message-display ${messageClasses.timestamp}`
|
||||
: "vc-timezones-profile-display";
|
||||
|
||||
return <>
|
||||
<Tooltip
|
||||
position="top"
|
||||
// @ts-ignore
|
||||
delay={750}
|
||||
allowOverflow={false}
|
||||
spacing={8}
|
||||
hideOnClick={true}
|
||||
tooltipClassName="vc-timezones-tooltip"
|
||||
hide={!longTime}
|
||||
text={longTime}
|
||||
>
|
||||
{toolTipProps => <>
|
||||
<span {...toolTipProps}
|
||||
className={classes}
|
||||
onClick={() => {
|
||||
toolTipProps.onClick();
|
||||
openTimezoneOverrideModal(props.userId);
|
||||
}}>
|
||||
{shortTimeFormatted}
|
||||
</span>
|
||||
</>}
|
||||
</Tooltip>
|
||||
</>;
|
||||
}
|
||||
|
||||
interface TimezoneOverrideModalProps {
|
||||
interface SetTimezoneOverrideModalProps {
|
||||
userId: string,
|
||||
modalProps: ModalProps,
|
||||
}
|
||||
|
||||
function SetTimezoneOverrideModal(props: TimezoneOverrideModalProps) {
|
||||
function SetTimezoneOverrideModal(props: SetTimezoneOverrideModalProps) {
|
||||
const [availableTimezones, setAvailableTimezones] = useState<SelectOption[]>();
|
||||
const [timezone, setTimezone] = useState<string | "NONE" | undefined>();
|
||||
|
||||
|
@ -174,7 +99,7 @@ function SetTimezoneOverrideModal(props: TimezoneOverrideModalProps) {
|
|||
<br />
|
||||
<br />
|
||||
To set your own Timezone for other users to see,
|
||||
click <Link onClick={/* TODO */ _ => _}>here</Link> to
|
||||
click <Link onClick={openTimezoneDBAuthModal}>here</Link> to
|
||||
authorize the public TimezoneDB API.
|
||||
</Text>
|
||||
|
||||
|
@ -207,11 +132,3 @@ function SetTimezoneOverrideModal(props: TimezoneOverrideModalProps) {
|
|||
</ModalFooter>
|
||||
</ModalRoot>;
|
||||
}
|
||||
|
||||
export function openTimezoneOverrideModal(userId: string) {
|
||||
openModal(modalProps => <>
|
||||
<ErrorBoundary>
|
||||
<SetTimezoneOverrideModal userId={userId} modalProps={modalProps} />
|
||||
</ErrorBoundary>
|
||||
</>);
|
||||
}
|
67
src/plugins/timezones/components/TimezoneDBAuthModal.tsx
Normal file
67
src/plugins/timezones/components/TimezoneDBAuthModal.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { ModalProps, openModal } from "@utils/modal";
|
||||
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common";
|
||||
|
||||
import { API_URL, getCurrentToken, verifyLogin } from "../api";
|
||||
import settings from "../settings";
|
||||
|
||||
const REDIRECT_URI: string = `${API_URL}/auth`;
|
||||
const CLIENT_ID: string = "922650528821940224";
|
||||
const SCOPES: string[] = ["identify"];
|
||||
|
||||
export async function openTimezoneDBAuthModal() {
|
||||
const token = getCurrentToken();
|
||||
if (token && await verifyLogin(token)) return; // TODO: open set current user modal
|
||||
|
||||
openModal(modalProps => <>
|
||||
<ErrorBoundary>
|
||||
<TimezoneDBAuthModal {...modalProps} />
|
||||
</ErrorBoundary>
|
||||
</>);
|
||||
}
|
||||
|
||||
function TimezoneDBAuthModal(modalProps: ModalProps): JSX.Element {
|
||||
return <OAuth2AuthorizeModal
|
||||
{...modalProps}
|
||||
scopes={SCOPES}
|
||||
responseType="code"
|
||||
redirectUri={REDIRECT_URI}
|
||||
permissions={0n}
|
||||
clientId={CLIENT_ID}
|
||||
cancelCompletesFlow={false}
|
||||
callback={async (response: { location: string }) => {
|
||||
try {
|
||||
const res = await fetch(response.location, {
|
||||
redirect: "manual",
|
||||
headers: {
|
||||
"Content-Type": VENCORD_USER_AGENT,
|
||||
},
|
||||
});
|
||||
|
||||
const { token } = await res.json() as { token: string };
|
||||
|
||||
if (!await verifyLogin(token)) {
|
||||
throw "Returned token was invalid!";
|
||||
}
|
||||
|
||||
settings.store.tokens = {
|
||||
[UserStore.getCurrentUser().id]: token,
|
||||
...settings.store.tokens,
|
||||
};
|
||||
|
||||
showToast("Successfully connected to TimezoneDB!", Toasts.Type.SUCCESS);
|
||||
} catch (e) {
|
||||
showToast("Failed to authorize TimezoneDB!", Toasts.Type.FAILURE);
|
||||
new Logger("Timezones").error("Failed to authorize TimezoneDB", e);
|
||||
}
|
||||
}}
|
||||
/>;
|
||||
}
|
|
@ -7,14 +7,12 @@
|
|||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { CogWheel } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, UserStore } from "@webpack/common";
|
||||
import { Message, User } from "discord-types/general";
|
||||
import { Promisable } from "type-fest";
|
||||
|
||||
import { verifyApi } from "./api";
|
||||
import { LocalTimestamp, openTimezoneOverrideModal } from "./components";
|
||||
import { LocalTimestamp } from "./components/LocalTimestamp";
|
||||
import { openTimezoneOverrideModal } from "./components/SetTimezoneOverrideModal";
|
||||
import settings, { SettingsComponent } from "./settings";
|
||||
|
||||
const contextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User }) => {
|
||||
|
@ -57,7 +55,7 @@ export default definePlugin({
|
|||
},
|
||||
})),
|
||||
{
|
||||
find: '"Message Username"',
|
||||
find: "\"Message Username\"",
|
||||
replacement: {
|
||||
match: /(?<=isVisibleOnlyOnHover.+?)id:.{1,11},timestamp.{1,50}}\),/,
|
||||
replace: "$&,$self.renderMessageTimezone(arguments[0]),",
|
||||
|
@ -70,20 +68,6 @@ export default definePlugin({
|
|||
"user-profile-overflow-menu": contextMenuPatch,
|
||||
},
|
||||
|
||||
beforeSave(options: Record<string, any>): Promisable<true | string> {
|
||||
// Check that API url is valid
|
||||
const { apiUrl } = options;
|
||||
if (!apiUrl) return "Invalid API url!";
|
||||
|
||||
return verifyApi(apiUrl).then(success => {
|
||||
if (success) return true;
|
||||
return "Failed to verify API!";
|
||||
}).catch(err => {
|
||||
new Logger("Timezones").info("Failed to verify API url", err);
|
||||
return "Failed to verify API!";
|
||||
});
|
||||
},
|
||||
|
||||
renderProfileTimezone: (props?: { user?: User; }) => {
|
||||
if (!settings.store.displayInProfile || !props?.user?.id) return null;
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Link } from "@components/Link";
|
||||
import { Margins } from "@utils/margins";
|
||||
|
@ -24,25 +23,24 @@ import { classes } from "@utils/misc";
|
|||
import { IPluginOptionComponentProps, OptionType } from "@utils/types";
|
||||
import { Text } from "@webpack/common";
|
||||
|
||||
import { DEFAULT_API, Snowflake } from "./api";
|
||||
import { TimezoneCache } from "./cache";
|
||||
import { Snowflake } from "./api";
|
||||
|
||||
/** A mapping of each user id to the override, being either a timezone or "disabled" */
|
||||
export type TimezoneOverrides = Record<Snowflake, string | null>;
|
||||
|
||||
/* A mapping of each authorized user id to the JWT token returned by the API. */
|
||||
export type TimezoneDBTokens = Record<Snowflake, string>;
|
||||
|
||||
const settings = definePluginSettings({
|
||||
enableApi: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Fetch user timezones from TimezoneDB when a local override does not exist",
|
||||
default: true,
|
||||
},
|
||||
apiUrl: {
|
||||
type: OptionType.STRING,
|
||||
description: "The TimezoneDB API instance",
|
||||
default: DEFAULT_API,
|
||||
placeholder: DEFAULT_API,
|
||||
onChange(_: string) {
|
||||
DataStore.clear(TimezoneCache).catch(_ => _);
|
||||
},
|
||||
tokens: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "Authorization with TimezoneDB",
|
||||
component: props => <AuthorizeTimezoneDBSetting {...props} />,
|
||||
},
|
||||
displayInChat: {
|
||||
type: OptionType.BOOLEAN,
|
||||
|
@ -57,21 +55,13 @@ const settings = definePluginSettings({
|
|||
timezoneOverrides: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "Local overrides for users' timezones",
|
||||
component: props => <>
|
||||
<TimezoneOverridesSetting
|
||||
setValue={props.setValue}
|
||||
setError={props.setError}
|
||||
option={props.option} />
|
||||
</>,
|
||||
component: props => <TimezoneOverridesSetting {...props} />,
|
||||
},
|
||||
});
|
||||
|
||||
export default settings;
|
||||
|
||||
export function SettingsComponent(): JSX.Element {
|
||||
// const { apiUrl } = settings.use(["apiUrl"]);
|
||||
// const url = `${apiUrl}/../?client_mod=${encodeURIComponent(VENCORD_USER_AGENT)}`;
|
||||
|
||||
return <>
|
||||
<Text variant="text-md/normal" className={classes(Margins.top16, Margins.bottom20)}>
|
||||
This plugin supports setting your own timezone publicly for others to
|
||||
|
@ -84,3 +74,7 @@ export function SettingsComponent(): JSX.Element {
|
|||
function TimezoneOverridesSetting(props: IPluginOptionComponentProps): JSX.Element {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function AuthorizeTimezoneDBSetting(props: IPluginOptionComponentProps): JSX.Element {
|
||||
return <></>;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue