mirror of
https://git.moonleay.net/Websites/liljudd-website.git
synced 2025-07-24 17:55:07 +02:00
340 lines
9.6 KiB
TypeScript
340 lines
9.6 KiB
TypeScript
import { getSession } from "@auth/solid-start";
|
|
import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
|
|
import { useLocation, useNavigate, useParams } from "@solidjs/router";
|
|
import { eq } from "drizzle-orm";
|
|
import moment from "moment-timezone";
|
|
import createClient from "openapi-fetch";
|
|
import {
|
|
For,
|
|
Index,
|
|
createEffect,
|
|
createResource,
|
|
createSignal,
|
|
} from "solid-js";
|
|
import { createStore } from "solid-js/store";
|
|
import { getRequestEvent } from "solid-js/web";
|
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
|
import Layout from "~/components/Layout";
|
|
import db from "~/drizzle";
|
|
import { accounts } from "~/drizzle/schema";
|
|
import { authOptions } from "~/server/auth";
|
|
import { paths } from "~/types/discord";
|
|
import "../../styles/pages/config.scss";
|
|
|
|
const guessTZ = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
|
|
const initialValue = (params: ReturnType<typeof useParams>) => ({
|
|
success: null as boolean | null,
|
|
guild: {
|
|
id: params.guildId,
|
|
name: undefined as string | undefined,
|
|
icon: undefined as string | null | undefined,
|
|
channel: "",
|
|
channels: [] as { id: string; name: string }[],
|
|
},
|
|
tzNames: [guessTZ()],
|
|
});
|
|
|
|
const getPayload = async (
|
|
id: string,
|
|
pathName: string,
|
|
): Promise<
|
|
| { success: false; message: string }
|
|
| (ReturnType<typeof initialValue> & { success: true })
|
|
> => {
|
|
"use server";
|
|
|
|
const event = getRequestEvent();
|
|
if (!event) return { success: false, message: "No request event!" };
|
|
|
|
const session = await getSession(event.request, authOptions);
|
|
if (!session?.user?.id)
|
|
return { success: false, message: "No user with id!" };
|
|
|
|
const { DISCORD_ACCESS_TOKEN } = (
|
|
await db
|
|
.selectDistinct({ DISCORD_ACCESS_TOKEN: accounts.access_token })
|
|
.from(accounts)
|
|
.where(eq(accounts.userId, session.user?.id))
|
|
.limit(1)
|
|
.execute()
|
|
)[0];
|
|
if (!DISCORD_ACCESS_TOKEN)
|
|
return { success: false, message: "No discord access token!" };
|
|
|
|
const { GET } = createClient<paths>({
|
|
baseUrl: "https://discord.com/api/v10",
|
|
});
|
|
const guildsRequest = await GET("/users/@me/guilds", {
|
|
headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` },
|
|
});
|
|
const channelsRequest = await GET("/guilds/{guild_id}/channels", {
|
|
params: {
|
|
path: {
|
|
guild_id: id,
|
|
},
|
|
},
|
|
headers: { Authorization: `Bot ${import.meta.env.VITE_DISCORD_BOT_TOKEN}` },
|
|
});
|
|
if (guildsRequest.error || channelsRequest.error) {
|
|
console.log(guildsRequest.error, channelsRequest.error, event.path);
|
|
return {
|
|
success: false,
|
|
message: "Error on one of the discord api requests!",
|
|
};
|
|
}
|
|
|
|
const guild = guildsRequest.data?.find((e) => e.id === id);
|
|
|
|
if (!guild)
|
|
return {
|
|
success: false,
|
|
message: "User is in no such guild with requested id!",
|
|
};
|
|
if (!(parseInt(guild.permissions) & (1 << 5)))
|
|
return {
|
|
success: false,
|
|
message:
|
|
"User is no MANAGE_GUILD permissions on this guild with requested id!",
|
|
};
|
|
|
|
let channels: ReturnType<typeof initialValue>["guild"]["channels"] = [];
|
|
channelsRequest.data?.forEach((channel) => {
|
|
if (channel.type !== 0) return;
|
|
channels.push({
|
|
id: channel.id,
|
|
name: channel.name,
|
|
});
|
|
});
|
|
|
|
console.log(
|
|
pathName,
|
|
pathName == event.path ? "server" : "client",
|
|
"success",
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
guild: {
|
|
id: guild.id,
|
|
name: guild.name,
|
|
icon: guild.icon,
|
|
// channel: "1162917335275950180",
|
|
channel: "",
|
|
channels,
|
|
},
|
|
tzNames: moment.tz.names(),
|
|
};
|
|
};
|
|
|
|
function config() {
|
|
const params = useParams();
|
|
const navigator = useNavigate();
|
|
const location = useLocation();
|
|
|
|
const [timezoneRef, setTimezoneRef] = createSignal<HTMLInputElement>();
|
|
const [timePlanningRef, setTimePlanningRef] =
|
|
createSignal<HTMLInputElement>();
|
|
const [channelRef, setChannelRef] = createSignal<HTMLSelectElement>();
|
|
const [pingableRolesRef, setPingableRolesRef] =
|
|
createSignal<HTMLInputElement>();
|
|
|
|
const [timezone, setTimezone] = createSignal(guessTZ());
|
|
const [payload] = createResource(
|
|
params.guildId,
|
|
async (id) => {
|
|
const payload = await getPayload(id, location.pathname).catch((e) =>
|
|
console.warn(e, id),
|
|
);
|
|
|
|
if (!payload) {
|
|
console.error(location.pathname, payload);
|
|
return initialValue(params);
|
|
}
|
|
|
|
if (!payload.success) {
|
|
console.log(payload);
|
|
console.log(location.pathname, payload.message, "No success");
|
|
navigator("/config", { replace: false });
|
|
return initialValue(params);
|
|
}
|
|
|
|
return payload;
|
|
},
|
|
{
|
|
initialValue: initialValue(params),
|
|
deferStream: true,
|
|
},
|
|
);
|
|
const [config, setConfig] = createStore({
|
|
features: {
|
|
timePlanning: {
|
|
enabled: false,
|
|
channelId: "833442323160891452",
|
|
pingableRoles: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
createEffect(() => {
|
|
const channelId = payload().guild.channel;
|
|
setConfig("features", "timePlanning", "channelId", channelId);
|
|
const ref = channelRef();
|
|
if (!ref) return;
|
|
ref.value = channelId;
|
|
});
|
|
createEffect(() => {
|
|
const ref = timezoneRef();
|
|
if (!ref) return;
|
|
ref.value = timezone();
|
|
});
|
|
createEffect(() => {
|
|
const ref = timePlanningRef();
|
|
if (!ref) return;
|
|
ref.checked = config.features.timePlanning.enabled;
|
|
});
|
|
createEffect(() => {
|
|
const ref = pingableRolesRef();
|
|
if (!ref) return;
|
|
ref.checked = config.features.timePlanning.pingableRoles;
|
|
});
|
|
|
|
return (
|
|
<Layout site="config">
|
|
<h3 class="text-center">Configure li'l Judd in</h3>
|
|
<div>
|
|
<div>
|
|
<div class="flex-row centered">
|
|
<img
|
|
class="guildpfp"
|
|
src={
|
|
payload().guild.icon
|
|
? `https://cdn.discordapp.com/icons/${payload().guild.id}/${
|
|
payload().guild.icon
|
|
}.webp?size=240`
|
|
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
|
}
|
|
alt="Server pfp"
|
|
/>
|
|
<h1>{payload().guild.name}</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<section>
|
|
<h2>Guild</h2>
|
|
<p>General settings for this guild.</p>
|
|
<div class="flex-row">
|
|
<label for="timezone">Timezone for your server:</label>
|
|
<input
|
|
type="text"
|
|
list="timezones"
|
|
id="timezone"
|
|
ref={(e) => setTimezoneRef(e)}
|
|
// disabled={!tzNames().find((e) => e === timezone())}
|
|
onInput={(e) => setTimezone(e.target.value)}
|
|
/>
|
|
|
|
<datalist id="timezones">
|
|
<Index each={payload().tzNames}>
|
|
{(zone) => <option value={zone()} />}
|
|
</Index>
|
|
</datalist>
|
|
|
|
<button
|
|
disabled={guessTZ() === timezone()}
|
|
title={"Detected: " + guessTZ()}
|
|
onClick={() => setTimezone(guessTZ())}
|
|
>
|
|
Auto-detect
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Features</h2>
|
|
<p>Configure the features of the bot</p>
|
|
<label for="timePlanning" class="flex-row">
|
|
<p>Time Planning </p>
|
|
<FontAwesomeIcon
|
|
icon={
|
|
config.features.timePlanning.enabled ? faToggleOn : faToggleOff
|
|
}
|
|
size="xl"
|
|
/>
|
|
</label>
|
|
<input
|
|
hidden
|
|
type="checkbox"
|
|
id="timePlanning"
|
|
ref={(e) => setTimePlanningRef(e)}
|
|
onInput={(e) =>
|
|
setConfig("features", "timePlanning", "enabled", e.target.checked)
|
|
}
|
|
/>
|
|
<div
|
|
class="sub"
|
|
classList={{ disabled: !config.features.timePlanning.enabled }}
|
|
>
|
|
<div class="flex-row">
|
|
<label>Target channel:</label>
|
|
<select
|
|
ref={(e) => setChannelRef(e)}
|
|
onInput={(e) =>
|
|
setConfig(
|
|
"features",
|
|
"timePlanning",
|
|
"channelId",
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option disabled value="">
|
|
--Select a Channel--
|
|
</option>
|
|
<For each={payload().guild.channels}>
|
|
{(channel) => (
|
|
<option value={channel.id}>{channel.name}</option>
|
|
)}
|
|
</For>
|
|
</select>
|
|
</div>
|
|
<div class="flex-row">
|
|
<label for="pingableRoles" class="flex-row">
|
|
<p>Enable pingable Roles:</p>
|
|
<FontAwesomeIcon
|
|
icon={
|
|
config.features.timePlanning.pingableRoles
|
|
? faToggleOn
|
|
: faToggleOff
|
|
}
|
|
size="xl"
|
|
/>
|
|
</label>
|
|
<input
|
|
hidden
|
|
type="checkbox"
|
|
id="pingableRoles"
|
|
ref={(e) => setPingableRolesRef(e)}
|
|
onInput={(e) =>
|
|
setConfig(
|
|
"features",
|
|
"timePlanning",
|
|
"pingableRoles",
|
|
e.target.checked,
|
|
)
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<button>Apply</button>
|
|
<button onClick={() => navigator("/config")}>Back</button>
|
|
</section>
|
|
</div>
|
|
</Layout>
|
|
);
|
|
}
|
|
|
|
export default config;
|