threading & locked applications

This commit is contained in:
nin0 2025-05-24 09:34:32 -04:00
parent 9191b156d7
commit 97d607871c
Signed by: nin0
SSH key fingerprint: SHA256:NOoDnFVvZNFvqfXCIhzr6oCTDImZAbTTuyAysZ8Ufk8
5 changed files with 259 additions and 137 deletions

View file

@ -1,4 +1,10 @@
export const Constants = {
PENDING_CHANNEL_ID: "1370539719842070683",
REJECTION_CHANNEL_ID: "1371073658403164261"
REJECTION_CHANNEL_ID: "1371073658403164261",
COLORS: {
NEW_REQ: 0xaaaaff,
BAD: 0xffaaaa,
YELLOW: 0xeadb77,
GOOD: 0xaaffaa
}
};

View file

@ -0,0 +1,47 @@
import { TextDisplay } from "components-jsx/TextDisplay";
import { Response } from "~/types";
export function ApplicationContent(props: {
response: Response;
includeDetails?: boolean;
}) {
const { response } = props;
return (
<TextDisplay>
### Application
<br />
Here for
<br />
-# {response.hereFor.join(", ")}
<br />
Found through
<br />
-#{" "}
{(() => {
switch (response.foundThrough) {
case "archive":
return "Guild Archive/nelly.tools";
case "tag":
return 'Hit "Ask to Join" on a clan tag';
case "user":
return "User invited them\n-# **Only accept if someone is mentioned in details**";
case "other":
return "Something else";
case "sold":
return "Was sold an invite\n-# **Only the owner can interact with this application**";
}
})()}
<br />
Languages
<br />
-# {response.languages}
<br />
Can boost
<br />
-# {response.canBoost ? "Yes" : "No"}
{props.includeDetails
? `\nDetails\n-# ${response.details.replaceAll("\n", "\n-# ")}`
: ""}
</TextDisplay>
);
}

View file

@ -0,0 +1,112 @@
import { ActionRow } from "components-jsx/ActionRow";
import { Button } from "components-jsx/Button";
import { ComponentMessage } from "components-jsx/ComponentMessage";
import { Container } from "components-jsx/Container";
import { Divider } from "components-jsx/Divider";
import { TextDisplay } from "components-jsx/TextDisplay";
import { ButtonStyles, User as OUser } from "oceanic.js";
import { Response } from "~/types";
import { User } from "./User";
import { Constants } from "~/Constants";
import { ApplicationContent } from "./ApplicationContent";
export function PendingApplicationMessage(props: {
id: string;
user: OUser;
response: Response;
locked?: boolean;
}) {
const { id, user, response } = props;
const locked = props.locked || false;
return (
<ComponentMessage>
<TextDisplay>{"<@&1375617676139040909>"}</TextDisplay>
<Container
accentColor={
!locked ? Constants.COLORS.NEW_REQ : Constants.COLORS.YELLOW
}
>
<TextDisplay>## New join request</TextDisplay>
<Divider />
<User user={user} />
<Divider />
<ApplicationContent response={response} />
<Divider />
<TextDisplay>
### Details
<br />
{response.details}
</TextDisplay>
<Divider />
<ActionRow>
<Button
style={ButtonStyles.SUCCESS}
customID={`accept-${id}`}
emoji={{
name: "blobcatgreen",
id: "1375806000312881233"
}}
disabled={locked}
>
Accept
</Button>
<Button
style={ButtonStyles.SUCCESS}
customID={`accept-friend-${id}`}
emoji={{
name: "blobcatgreen",
id: "1375806000312881233"
}}
disabled={locked}
>
Accept + Friend
</Button>
</ActionRow>
<ActionRow>
<Button
style={ButtonStyles.DANGER}
customID={`reject-${id}`}
emoji={{
name: "blobcatred",
id: "1375806202470203513"
}}
disabled={locked}
>
Deny
</Button>
<Button
style={ButtonStyles.DANGER}
customID={`reject-ban-${id}`}
emoji={{
name: "BAN",
id: "1375806319621046313"
}}
disabled={locked}
>
Deny + Ban
</Button>
</ActionRow>
<ActionRow>
<Button
style={ButtonStyles.SECONDARY}
customID={`interview-${id}`}
emoji={{
name: "💬"
}}
>
Interview
</Button>
<Button
style={ButtonStyles.SECONDARY}
customID={`${locked ? "un" : ""}lock-${id}`}
emoji={{
name: "🔑"
}}
>
{!locked ? "Lock" : "Unlock"}
</Button>
</ActionRow>
</Container>
</ComponentMessage>
);
}

View file

@ -16,6 +16,8 @@ import { selfappReq } from "./selfappReq";
import { User } from "./components/User";
import { Response } from "./types";
import { match } from "./utils";
import { PendingApplicationMessage } from "./components/PendingApplicationMessage";
import { ApplicationContent } from "./components/ApplicationContent";
export async function setupJoinRequestHandler(shard: Shard) {
shard.ws?.on("message", async (d) => {
@ -52,140 +54,51 @@ export async function setupJoinRequestHandler(shard: Shard) {
const pendingMsg =
await client.rest.channels.createMessage(
Constants.PENDING_CHANNEL_ID,
<ComponentMessage>
<TextDisplay>
{"<@&1375617676139040909>"}
</TextDisplay>
<Container accentColor={0xaaaaff}>
<TextDisplay>
## New join request
</TextDisplay>
<Divider />
<User user={user} />
<Divider />
<TextDisplay>
### Application
<br />
Here for
<br />
-# {response.hereFor.join(", ")}
<br />
Found through
<br />
-#{" "}
{(() => {
switch (
response.foundThrough
) {
case "archive":
return "Guild Archive/nelly.tools";
case "tag":
return 'Hit "Ask to Join" on a clan tag';
case "user":
return "User invited them\n-# **Only accept if someone is mentioned in details**";
case "other":
return "Something else";
case "sold":
return "Was sold an invite\n-# **Only the owner can interact with this application**";
<PendingApplicationMessage
id={applicationData.request.id}
response={response}
user={user}
locked={
response.foundThrough === "sold"
}
})()}
<br />
Languages
<br />
-# {response.languages}
<br />
Can boost
<br />
-#{" "}
{response.canBoost
? "Yes"
: "No"}
</TextDisplay>
<Divider />
<TextDisplay>
### Details
<br />
{response.details}
</TextDisplay>
<Divider />
<ActionRow>
<Button
style={ButtonStyles.SUCCESS}
customID={`accept-${applicationData.request.id}`}
emoji={{
name: "blobcatgreen",
id: "1375806000312881233"
}}
>
Accept
</Button>
<Button
style={ButtonStyles.SUCCESS}
customID={`accept-friend-${applicationData.request.id}`}
emoji={{
name: "blobcatgreen",
id: "1375806000312881233"
}}
>
Accept + Friend
</Button>
</ActionRow>
<ActionRow>
<Button
style={ButtonStyles.DANGER}
customID={`reject-${applicationData.request.id}`}
emoji={{
name: "blobcatred",
id: "1375806202470203513"
}}
>
Deny
</Button>
<Button
style={ButtonStyles.DANGER}
customID={`reject-ban-${applicationData.request.id}`}
emoji={{
name: "BAN",
id: "1375806319621046313"
}}
>
Deny + Ban
</Button>
</ActionRow>
<ActionRow>
<Button
style={
ButtonStyles.SECONDARY
}
customID={`interview-${applicationData.request.id}`}
emoji={{
name: "💬"
}}
>
Interview
</Button>
<Button
style={
ButtonStyles.SECONDARY
}
customID={`lock-${applicationData.request.id}`}
emoji={{
name: "🔑"
}}
>
Lock
</Button>
</ActionRow>
</Container>
</ComponentMessage>
/>
);
const thread = await pendingMsg.startThread({
name: `${user.tag}'s application`,
autoArchiveDuration: 10080
});
const fullApplicantProfile: any = await selfappReq(
`/users/${user.id}/profile`,
"GET",
{
join_request_id: applicationData.request.id
}
);
thread.createMessage({
embeds: [
{
color: Constants.COLORS.NEW_REQ,
title: "User bio",
description:
"```\n" +
fullApplicantProfile.user_profile
.bio +
"\n```"
}
]
});
await db.run(
"INSERT INTO applications (id, user_id, status, message_id, channel_id, responses) VALUES (?, ?, 0, ?, ?, ?)",
"INSERT INTO applications (id, user_id, status, message_id, channel_id, responses, thread_id) VALUES (?, ?, 0, ?, ?, ?, ?)",
applicationData.request.id,
applicationData.request.user_id,
pendingMsg.id,
pendingMsg.channelID,
JSON.stringify(response)
JSON.stringify(response),
thread.id
);
break;
@ -198,6 +111,10 @@ export async function setupJoinRequestHandler(shard: Shard) {
"SELECT * FROM applications WHERE id=?",
applicationData.id
);
const user = await client.rest.users.get(
applicationData.user_id
);
if (application) {
await db.run(
"DELETE FROM applications WHERE id=?",
@ -208,30 +125,59 @@ export async function setupJoinRequestHandler(shard: Shard) {
application.message_id
);
}
const user = await client.rest.users.get(
applicationData.user_id
const response: Response = JSON.parse(
application.responses
);
await client.rest.channels.createMessage(
const msg = await client.rest.channels.createMessage(
Constants.REJECTION_CHANNEL_ID,
<ComponentMessage>
<Container accentColor={0xffaaaa}>
<Container accentColor={Constants.COLORS.BAD}>
<TextDisplay>
## Join request withdrawn
</TextDisplay>
<Divider />
<User user={user} />
<Divider />
<TextDisplay>### Application</TextDisplay>
{application ? (
<TextDisplay>User has applied</TextDisplay>
<ApplicationContent
response={response}
includeDetails={true}
/>
) : (
<TextDisplay>
### Application
<br />
User hasn't applied
</TextDisplay>
)}
</Container>
</ComponentMessage>
);
await client.rest.request({
auth: `Bot ${process.env.BOT_TOKEN}`,
method: "POST",
path: `/channels/${application.thread_id}/messages`,
json: {
content: "",
flags: 0,
message_reference: {
channel_id: msg.channelID,
guild_id: msg.guildID!,
message_id: msg.id,
type: 1
}
}
});
if (application)
await client.rest.channels.edit(application.thread_id, {
locked: true,
reason: `${user.tag}'s application has been withdrawn`
});
break;
}
}

View file

@ -3,12 +3,23 @@ export async function selfappReq(
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
data?: object
) {
return await fetch("https://discord.com/api/v9" + path, {
const url =
"https://discord.com/api/v9" +
path +
(() => {
if (!data || method !== "GET") return "";
// @ts-expect-error
return `?${new URLSearchParams(data).toString()}`;
})();
console.log(url);
return await fetch(url, {
method,
headers: {
Authorization: process.env.USER_TOKEN!,
...(data ? { "Content-Type": "application/json" } : {})
},
body: data ? JSON.stringify(data || {}) : null
body: data && method !== "GET" ? JSON.stringify(data || {}) : null
}).then((e) => e.json());
}