2021-08-31 00:00:45 -04:00
|
|
|
import { Key, Clipboard, Globe, Plus } from "@styled-icons/boxicons-regular";
|
2021-08-30 12:18:42 -04:00
|
|
|
import { LockAlt } from "@styled-icons/boxicons-solid";
|
2021-08-12 10:29:19 -04:00
|
|
|
import { observer } from "mobx-react-lite";
|
|
|
|
import { Bot } from "revolt-api/types/Bots";
|
2021-08-30 22:33:36 -04:00
|
|
|
import { User } from "revolt.js/dist/maps/Users";
|
2021-08-30 12:18:42 -04:00
|
|
|
import styled from "styled-components";
|
2021-08-12 10:29:19 -04:00
|
|
|
|
2021-08-30 12:18:42 -04:00
|
|
|
import styles from "./Panes.module.scss";
|
|
|
|
import { Text } from "preact-i18n";
|
2021-08-12 10:29:19 -04:00
|
|
|
import { useEffect, useState } from "preact/hooks";
|
|
|
|
|
2021-08-30 22:33:36 -04:00
|
|
|
import { internalEmit } from "../../../lib/eventEmitter";
|
2021-08-30 23:23:07 -04:00
|
|
|
import { stopPropagation } from "../../../lib/stopPropagation";
|
2021-08-30 12:18:42 -04:00
|
|
|
|
2021-08-12 11:07:41 -04:00
|
|
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
2021-08-30 21:42:50 -04:00
|
|
|
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
2021-08-12 10:29:19 -04:00
|
|
|
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
|
|
|
|
2021-08-30 12:18:42 -04:00
|
|
|
import Tooltip from "../../../components/common/Tooltip";
|
|
|
|
import UserIcon from "../../../components/common/user/UserIcon";
|
2021-08-12 10:29:19 -04:00
|
|
|
import Button from "../../../components/ui/Button";
|
|
|
|
import Checkbox from "../../../components/ui/Checkbox";
|
|
|
|
import InputBox from "../../../components/ui/InputBox";
|
|
|
|
import Tip from "../../../components/ui/Tip";
|
2021-08-30 12:18:42 -04:00
|
|
|
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
2021-08-12 10:29:19 -04:00
|
|
|
|
|
|
|
interface Data {
|
|
|
|
_id: string;
|
|
|
|
username: string;
|
|
|
|
public: boolean;
|
|
|
|
interactions_url?: string;
|
|
|
|
}
|
|
|
|
|
2021-08-30 15:10:55 -04:00
|
|
|
interface Changes {
|
|
|
|
name?: string;
|
|
|
|
public?: boolean;
|
|
|
|
interactions_url?: string;
|
|
|
|
remove?: "InteractionsURL";
|
|
|
|
}
|
|
|
|
|
2021-08-30 12:18:42 -04:00
|
|
|
const BotBadge = styled.div`
|
|
|
|
display: inline-block;
|
|
|
|
|
|
|
|
height: 1.3em;
|
|
|
|
padding: 0px 4px;
|
|
|
|
font-size: 0.7em;
|
|
|
|
user-select: none;
|
|
|
|
margin-inline-start: 2px;
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
|
|
color: var(--foreground);
|
|
|
|
background: var(--accent);
|
|
|
|
border-radius: calc(var(--border-radius) / 2);
|
|
|
|
`;
|
|
|
|
|
2021-08-30 15:10:55 -04:00
|
|
|
interface Props {
|
|
|
|
bot: Bot;
|
2021-08-30 21:42:50 -04:00
|
|
|
onDelete(): void;
|
|
|
|
onUpdate(changes: Changes): void;
|
2021-08-30 15:10:55 -04:00
|
|
|
}
|
|
|
|
|
2021-08-30 21:42:50 -04:00
|
|
|
function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|
|
|
const client = useClient();
|
|
|
|
const [user, setUser] = useState<User>(client.users.get(bot._id)!);
|
2021-08-30 15:10:55 -04:00
|
|
|
const [data, setData] = useState<Data>({
|
|
|
|
_id: bot._id,
|
2021-08-30 21:42:50 -04:00
|
|
|
username: user.username,
|
2021-08-30 15:10:55 -04:00
|
|
|
public: bot.public,
|
|
|
|
interactions_url: bot.interactions_url,
|
|
|
|
});
|
2021-08-30 23:23:07 -04:00
|
|
|
const [error, setError] = useState<string | JSX.Element>("");
|
2021-08-30 15:10:55 -04:00
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const [editMode, setEditMode] = useState(false);
|
|
|
|
const [usernameRef, setUsernameRef] = useState<HTMLInputElement | null>(
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
const [interactionsRef, setInteractionsRef] =
|
|
|
|
useState<HTMLInputElement | null>(null);
|
|
|
|
const { writeClipboard, openScreen } = useIntermediate();
|
2021-08-12 10:29:19 -04:00
|
|
|
|
2021-08-30 15:10:55 -04:00
|
|
|
async function save() {
|
|
|
|
const changes: Changes = {};
|
|
|
|
if (data.username !== user!.username) changes.name = data.username;
|
2021-08-12 10:29:19 -04:00
|
|
|
if (data.public !== bot.public) changes.public = data.public;
|
2021-08-30 21:42:50 -04:00
|
|
|
if (data.interactions_url === "") changes.remove = "InteractionsURL";
|
2021-08-30 15:10:55 -04:00
|
|
|
else if (data.interactions_url !== bot.interactions_url)
|
2021-08-12 10:29:19 -04:00
|
|
|
changes.interactions_url = data.interactions_url;
|
2021-08-30 15:10:55 -04:00
|
|
|
setSaving(true);
|
2021-08-30 23:23:07 -04:00
|
|
|
setError("");
|
2021-08-30 15:10:55 -04:00
|
|
|
try {
|
2021-08-30 21:42:50 -04:00
|
|
|
await client.bots.edit(bot._id, changes);
|
|
|
|
onUpdate(changes);
|
2021-08-30 15:10:55 -04:00
|
|
|
setEditMode(false);
|
|
|
|
} catch (e) {
|
2021-08-30 23:23:07 -04:00
|
|
|
if (e.isAxiosError && e.response.data?.type) {
|
|
|
|
switch (e.response.data.type) {
|
|
|
|
case "UsernameTaken":
|
|
|
|
setError("That username is taken!");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
setError(`Error: ${e.response.data.type}`);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else setError(e.toString());
|
2021-08-30 15:10:55 -04:00
|
|
|
}
|
|
|
|
setSaving(false);
|
2021-08-12 10:29:19 -04:00
|
|
|
}
|
|
|
|
|
2021-08-30 21:42:50 -04:00
|
|
|
async function editBotAvatar(avatar?: string) {
|
|
|
|
setSaving(true);
|
2021-08-30 23:23:07 -04:00
|
|
|
setError("");
|
2021-08-30 21:42:50 -04:00
|
|
|
await client.request("PATCH", "/users/id", {
|
|
|
|
headers: { "x-bot-token": bot.token },
|
|
|
|
transformRequest: (data, headers) => {
|
|
|
|
// Remove user headers for this request
|
|
|
|
delete headers["x-user-id"];
|
|
|
|
delete headers["x-session-token"];
|
|
|
|
return data;
|
|
|
|
},
|
|
|
|
data: JSON.stringify(avatar ? { avatar } : { remove: "Avatar" }),
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = await client.bots.fetch(bot._id);
|
|
|
|
if (!avatar) res.user.update({}, "Avatar");
|
|
|
|
setUser(res.user);
|
|
|
|
setSaving(false);
|
|
|
|
}
|
|
|
|
|
2021-08-12 10:29:19 -04:00
|
|
|
return (
|
2021-08-30 23:23:07 -04:00
|
|
|
<div key={bot._id} className={styles.botCard}>
|
2021-08-30 15:10:55 -04:00
|
|
|
<div className={styles.infoheader}>
|
|
|
|
<div className={styles.container}>
|
2021-08-30 21:42:50 -04:00
|
|
|
{!editMode ? (
|
|
|
|
<UserIcon
|
|
|
|
className={styles.avatar}
|
|
|
|
target={user}
|
|
|
|
size={48}
|
|
|
|
onClick={() =>
|
|
|
|
openScreen({
|
|
|
|
id: "profile",
|
|
|
|
user_id: user._id,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<FileUploader
|
|
|
|
width={64}
|
|
|
|
height={64}
|
|
|
|
style="icon"
|
|
|
|
fileType="avatars"
|
|
|
|
behaviour="upload"
|
|
|
|
maxFileSize={4_000_000}
|
|
|
|
onUpload={(avatar) => editBotAvatar(avatar)}
|
|
|
|
remove={() => editBotAvatar()}
|
|
|
|
defaultPreview={user.generateAvatarURL(
|
|
|
|
{ max_side: 256 },
|
|
|
|
true,
|
|
|
|
)}
|
|
|
|
previewURL={user.generateAvatarURL(
|
|
|
|
{ max_side: 256 },
|
|
|
|
true,
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
2021-08-30 15:10:55 -04:00
|
|
|
{!editMode ? (
|
|
|
|
<div className={styles.userDetail}>
|
|
|
|
<div className={styles.userName}>
|
|
|
|
{user!.username}{" "}
|
|
|
|
<BotBadge>
|
|
|
|
<Text id="app.main.channel.bot" />
|
|
|
|
</BotBadge>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className={styles.userid}>
|
|
|
|
<Tooltip
|
|
|
|
content={<Text id="app.special.copy" />}>
|
|
|
|
<a
|
|
|
|
onClick={() =>
|
|
|
|
writeClipboard(user!._id)
|
|
|
|
}>
|
|
|
|
{user!._id}
|
|
|
|
</a>
|
|
|
|
</Tooltip>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<InputBox
|
|
|
|
ref={setUsernameRef}
|
|
|
|
value={data.username}
|
|
|
|
disabled={saving}
|
|
|
|
onChange={(e) =>
|
|
|
|
setData({
|
|
|
|
...data,
|
|
|
|
username: e.currentTarget.value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{!editMode && (
|
|
|
|
<Tooltip
|
|
|
|
content={
|
|
|
|
bot.public
|
|
|
|
? "Bot is public. Anyone can invite it."
|
|
|
|
: "Bot is private. Only you can invite it."
|
|
|
|
}>
|
|
|
|
{bot.public ? (
|
|
|
|
<Globe size={24} />
|
|
|
|
) : (
|
|
|
|
<LockAlt size={24} />
|
|
|
|
)}
|
|
|
|
</Tooltip>
|
|
|
|
)}
|
|
|
|
<Button
|
2021-08-30 21:42:50 -04:00
|
|
|
disabled={saving}
|
2021-08-30 15:10:55 -04:00
|
|
|
onClick={() => {
|
|
|
|
if (editMode) {
|
|
|
|
setData({
|
|
|
|
_id: bot._id,
|
|
|
|
username: user!.username,
|
|
|
|
public: bot.public,
|
|
|
|
interactions_url: bot.interactions_url,
|
|
|
|
});
|
|
|
|
usernameRef!.value = user!.username;
|
|
|
|
interactionsRef!.value = bot.interactions_url || "";
|
2021-08-30 23:23:07 -04:00
|
|
|
setError("");
|
2021-08-30 15:10:55 -04:00
|
|
|
setEditMode(false);
|
|
|
|
} else setEditMode(true);
|
|
|
|
}}
|
|
|
|
contrast>
|
2021-08-31 00:50:48 -04:00
|
|
|
{editMode ? <Text id="app.special.modals.actions.cancel" /> : "Edit"}
|
2021-08-30 15:10:55 -04:00
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
{!editMode && (
|
|
|
|
<CategoryButton
|
|
|
|
account
|
|
|
|
icon={<Key size={24} />}
|
|
|
|
onClick={() => writeClipboard(bot.token)}
|
|
|
|
description={
|
|
|
|
<>
|
|
|
|
{"••••••••••••••••••••••••••••••••••••"}{" "}
|
|
|
|
<a
|
|
|
|
onClick={(ev) =>
|
|
|
|
stopPropagation(
|
|
|
|
ev,
|
|
|
|
openScreen({
|
|
|
|
id: "token_reveal",
|
|
|
|
token: bot.token,
|
|
|
|
username: user!.username,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
}>
|
|
|
|
<Text id="app.special.modals.actions.reveal" />
|
|
|
|
</a>
|
|
|
|
</>
|
2021-08-12 10:29:19 -04:00
|
|
|
}
|
2021-08-30 15:10:55 -04:00
|
|
|
action={<Clipboard size={18} />}>
|
|
|
|
Token
|
|
|
|
</CategoryButton>
|
|
|
|
)}
|
|
|
|
{editMode && (
|
2021-08-30 23:23:07 -04:00
|
|
|
<div className={styles.botSection}>
|
2021-08-30 15:10:55 -04:00
|
|
|
<Checkbox
|
|
|
|
checked={data.public}
|
|
|
|
disabled={saving}
|
|
|
|
contrast
|
|
|
|
description="Whether to allow other users to invite this bot."
|
|
|
|
onChange={(v) => setData({ ...data, public: v })}>
|
|
|
|
Public Bot
|
|
|
|
</Checkbox>
|
|
|
|
<h3>Interactions URL</h3>
|
|
|
|
<h5>This field is reserved for the future.</h5>
|
|
|
|
<InputBox
|
|
|
|
ref={setInteractionsRef}
|
|
|
|
value={data.interactions_url}
|
|
|
|
disabled={saving}
|
|
|
|
onChange={(e) =>
|
|
|
|
setData({
|
|
|
|
...data,
|
2021-08-30 21:42:50 -04:00
|
|
|
interactions_url: e.currentTarget.value,
|
2021-08-30 15:10:55 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
/>
|
2021-08-30 23:23:07 -04:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{error && (
|
|
|
|
<div className={styles.botSection}>
|
|
|
|
<Tip error hideSeparator>
|
|
|
|
{error}
|
|
|
|
</Tip>
|
|
|
|
</div>
|
2021-08-30 15:10:55 -04:00
|
|
|
)}
|
|
|
|
|
|
|
|
<div className={styles.buttonRow}>
|
|
|
|
{editMode && (
|
|
|
|
<>
|
|
|
|
<Button accent onClick={save}>
|
2021-08-31 00:50:48 -04:00
|
|
|
<Text id="app.special.modals.actions.save" />
|
2021-08-30 15:10:55 -04:00
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
error
|
2021-08-30 21:42:50 -04:00
|
|
|
onClick={async () => {
|
|
|
|
setSaving(true);
|
|
|
|
await client.bots.delete(bot._id);
|
|
|
|
onDelete();
|
|
|
|
}}>
|
2021-08-31 00:50:48 -04:00
|
|
|
<Text id="app.special.modals.actions.delete" />
|
2021-08-30 15:10:55 -04:00
|
|
|
</Button>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{!editMode && (
|
2021-08-30 22:33:36 -04:00
|
|
|
<>
|
|
|
|
<Button
|
|
|
|
onClick={() =>
|
|
|
|
writeClipboard(
|
|
|
|
`${window.origin}/bot/${bot._id}`,
|
|
|
|
)
|
|
|
|
}>
|
|
|
|
Copy Invite Link
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
accent
|
|
|
|
onClick={() =>
|
|
|
|
internalEmit(
|
|
|
|
"Intermediate",
|
|
|
|
"navigate",
|
|
|
|
`/bot/${bot._id}`,
|
|
|
|
)
|
|
|
|
}>
|
|
|
|
Add Bot
|
|
|
|
</Button>
|
|
|
|
</>
|
2021-08-30 15:10:55 -04:00
|
|
|
)}
|
|
|
|
</div>
|
2021-08-12 10:29:19 -04:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const MyBots = observer(() => {
|
|
|
|
const client = useClient();
|
|
|
|
const [bots, setBots] = useState<Bot[] | undefined>(undefined);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
client.bots.fetchOwned().then(({ bots }) => setBots(bots));
|
|
|
|
// eslint-disable-next-line
|
|
|
|
}, []);
|
|
|
|
|
2021-08-31 00:00:45 -04:00
|
|
|
const { openScreen } = useIntermediate();
|
2021-08-12 10:29:19 -04:00
|
|
|
|
|
|
|
return (
|
2021-08-30 12:18:42 -04:00
|
|
|
<div className={styles.myBots}>
|
2021-08-31 00:00:45 -04:00
|
|
|
<CategoryButton
|
|
|
|
account
|
|
|
|
icon={<Plus size={24} />}
|
|
|
|
onClick={() =>
|
|
|
|
openScreen({
|
|
|
|
id: "create_bot",
|
|
|
|
onCreate: (bot) => setBots([...(bots ?? []), bot]),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
action="chevron">
|
|
|
|
Create a Bot
|
|
|
|
</CategoryButton>
|
2021-08-12 10:29:19 -04:00
|
|
|
{bots?.map((bot) => {
|
|
|
|
return (
|
2021-08-30 15:10:55 -04:00
|
|
|
<BotCard
|
2021-08-12 10:29:19 -04:00
|
|
|
key={bot._id}
|
2021-08-30 15:10:55 -04:00
|
|
|
bot={bot}
|
|
|
|
onDelete={() =>
|
2021-08-30 21:42:50 -04:00
|
|
|
setBots(bots.filter((x) => x._id !== bot._id))
|
2021-08-30 15:10:55 -04:00
|
|
|
}
|
|
|
|
onUpdate={(changes: Changes) =>
|
2021-08-30 21:42:50 -04:00
|
|
|
setBots(
|
2021-08-30 15:10:55 -04:00
|
|
|
bots.map((x) => {
|
|
|
|
if (x._id === bot._id) {
|
2021-08-30 21:42:50 -04:00
|
|
|
if (
|
|
|
|
"public" in changes &&
|
|
|
|
typeof changes.public === "boolean"
|
|
|
|
)
|
|
|
|
x.public = changes.public;
|
|
|
|
if ("interactions_url" in changes)
|
|
|
|
x.interactions_url =
|
|
|
|
changes.interactions_url;
|
|
|
|
if (
|
|
|
|
changes.remove === "InteractionsURL"
|
|
|
|
)
|
|
|
|
x.interactions_url = undefined;
|
2021-08-30 15:10:55 -04:00
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}),
|
2021-08-30 21:42:50 -04:00
|
|
|
)
|
2021-08-30 15:10:55 -04:00
|
|
|
}
|
|
|
|
/>
|
2021-08-12 10:29:19 -04:00
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
});
|