Skip to content

Commit

Permalink
Add quotes hook
Browse files Browse the repository at this point in the history
  • Loading branch information
ashleylamont committed Sep 17, 2024
1 parent d0c1053 commit b4300d3
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 74 deletions.
123 changes: 81 additions & 42 deletions src/commands/quote.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {
ApplicationCommandType,
Channel,
ContextMenuCommandBuilder,
EmbedBuilder,
Guild,
GuildMember,
type MessageContextMenuCommandInteraction,
PermissionsBitField,
User,
} from "discord.js";

export const data = new ContextMenuCommandBuilder()
Expand All @@ -24,51 +27,87 @@ export async function execute(
}
const quoteTarget = interaction.targetMessage;

if (
!quotesChannel
.permissionsFor(interaction.member)
.has(PermissionsBitField.Flags.SendMessages)
) {
await interaction.reply({
content: "You do not have permission to use this command.",
ephemeral: true,
});
return;
}

await quotesChannel.send({
embeds: [
new EmbedBuilder()
.setTitle(
`Quote of ${quoteTarget?.member?.displayName ?? quoteTarget?.author.username}`,
)
.setDescription(quoteTarget?.content)
.setURL(quoteTarget?.url)
.setTimestamp(quoteTarget?.createdAt)
// eslint-disable-next-line unicorn/no-null
.setColor(quoteTarget?.member?.displayHexColor ?? null)
.setAuthor({
name:
quoteTarget?.member?.displayName ?? quoteTarget?.author.username,
iconURL: quoteTarget?.author.avatarURL({ size: 64 }) ?? "",
})
.setFields(
{
name: "Quoted in",
value: `<#${interaction.channel?.id ?? "Not found"}>`,
inline: true,
},
{
name: "Quoted by",
value: `<@${interaction.member?.id ?? "Not found"}>`,
inline: true,
},
),
],
await sendQuote(interaction.guild, {
message: quoteTarget.content,
channel: quoteTarget.channel,
quotee: quoteTarget.member
? [quoteTarget.author, quoteTarget.member]
: quoteTarget.author.username,
quoter: interaction.member,
timestamp: quoteTarget.createdAt,
});

await interaction.reply({
content: "Quote sent to #quotes",
ephemeral: true,
});
}

export interface Quote {
message: string;
channel?: Channel;
quotee?: string | [User, GuildMember];
quoter?: GuildMember;
url?: string;
timestamp: Date;
}

export async function sendQuote(guild: Guild, quote: Quote) {
const quotesChannel = await guild.channels.fetch("1169166790094491678");
if (quotesChannel === null) {
throw new Error("Quotes channel is null");
} else if (!quotesChannel.isTextBased()) {
throw new Error("Quotes channel is not text based");
}

const embed = new EmbedBuilder()
.setDescription(quote.message)
.setTimestamp(quote.timestamp);

if (quote.channel) {
embed.addFields({
name: "Quoted in",
value: `<#${quote.channel?.id ?? "Not found"}>`,
inline: true,
});
}

if (quote.quoter && quote.quoter.id) {
embed.addFields({
name: "Quoted by",
value: `<@${quote.quoter.id}>`,
inline: true,
});
}

if (quote.url) {
embed.setURL(quote.url);
}

if (Array.isArray(quote.quotee)) {
embed.setColor(quote.quotee[1].displayHexColor);
}

if (typeof quote.quotee === "string") {
embed.setTitle(`Quote of ${quote.quotee}`);
embed.setAuthor({
name: quote.quotee,
});
} else if (Array.isArray(quote.quotee)) {
const [user, member] = quote.quotee;
embed.setTitle(`Quote of ${member.displayName ?? user.username}`);
embed.setAuthor({
name: member?.displayName ?? user.username,
iconURL: user.avatarURL({ size: 64 }) ?? "",
});
} else {
embed.setTitle(`Anonymous Quote`);
embed.setAuthor({
name: "Anonymous",
});
}

return quotesChannel.send({
embeds: [embed],
});
}
75 changes: 57 additions & 18 deletions src/nocodb-integration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { NocoDBWebhook, DBGetRowsResponse, DBItem } from "./nocodb-types";
import {
NocoDBWebhook,
DBGetRowsResponse,
MembershipDBItem,
QuoteDBItem,
} from "./nocodb-types";
import express, { Express, Request } from "express";
import { GuildMember, Snowflake } from "discord.js";
import { sendQuote } from "./commands/quote";

const MEMBER_ROLE_ID = "753524901708693558";
const LIFE_MEMBER_ROLE_ID = "702889882598506558";
Expand All @@ -24,14 +30,14 @@ function transformUsername(
return [name, Number.parseInt(discriminator, 10)];
}

interface DBItemDiscord {
item: DBItem;
interface MembershipDBItemWithDiscordGuildMember {
item: MembershipDBItem;
discord: GuildMember;
}

async function getDatabaseItemDiscord(
item: DBItem,
): Promise<DBItemDiscord | undefined> {
item: MembershipDBItem,
): Promise<MembershipDBItemWithDiscordGuildMember | undefined> {
try {
return {
item,
Expand All @@ -48,15 +54,20 @@ export async function refreshDBData() {
// Force update all discord members
await Promise.all([cssaGuild.fetch(), cssaGuild.members.fetch()]);

const databaseResp: DBGetRowsResponse = await fetch(DB_REQUEST_URL, {
headers: new Headers({
"xc-token": process.env.NOCODB_TOKEN || "",
}),
}).then((response) => response.json());
const databaseResp: DBGetRowsResponse<MembershipDBItem> = await fetch(
DB_REQUEST_URL,
{
headers: new Headers({
"xc-token": process.env.NOCODB_TOKEN || "",
}),
},
).then((response) => response.json());
const fetchedData = databaseResp.list;

// Associate a discord user for each entry
const itemsDiscordPromises: Promise<DBItemDiscord | undefined>[] = [];
const itemsDiscordPromises: Promise<
MembershipDBItemWithDiscordGuildMember | undefined
>[] = [];
for (const row of fetchedData) {
itemsDiscordPromises.push(getDatabaseItemDiscord(row));
}
Expand Down Expand Up @@ -101,16 +112,40 @@ export async function attachNocoDBWebhookListener(expressApp: Express) {

expressApp.use(express.json());

expressApp.post(
"/quote",
async (request: Request<NocoDBWebhook<QuoteDBItem>>, response) => {
if (request.headers["x-cssa-secret"] != process.env.WEBSOCKET_SECRET) {
console.warn("Illegal websocket update.");
response.status(401).send();
return;
}
console.log("Received db webhook.");
const webhookBody: NocoDBWebhook<QuoteDBItem> = request.body;

const row = webhookBody.data.rows[0];
if (!row) {
console.error("No quote data in webhook.");
response.status(204).send();
return;
}

await onQuoteSubmission(row);

response.status(204).send();
},
);

expressApp.post(
"/membership/update",
async (request: Request<NocoDBWebhook>, response) => {
async (request: Request<NocoDBWebhook<MembershipDBItem>>, response) => {
if (request.headers["x-cssa-secret"] != process.env.WEBSOCKET_SECRET) {
console.warn("Illegal websocket update.");
response.status(401).send();
return;
}
console.log("Received db webhook.");
const webhookBody: NocoDBWebhook = request.body;
const webhookBody: NocoDBWebhook<MembershipDBItem> = request.body;

// Collect all async operations for better concurrency
const promises: Promise<void>[] = [];
Expand Down Expand Up @@ -208,11 +243,7 @@ export async function performRoleUpdate(
: member.roles.remove(roleId));
}

export async function onRowUpdateWebhook(row: DBItem) {
getUser(row.discord_username);
}

export async function onRowUpdate(row: DBItemDiscord) {
export async function onRowUpdate(row: MembershipDBItemWithDiscordGuildMember) {
const discord = row.discord;
await performRoleUpdate(discord, MEMBER_ROLE_ID, "add");
if (row.item.life_member) {
Expand All @@ -223,3 +254,11 @@ export async function onRowUpdate(row: DBItemDiscord) {
export async function onRowDelete(row: GuildMember) {
await performRoleUpdate(row, MEMBER_ROLE_ID, "remove");
}

export async function onQuoteSubmission(row: QuoteDBItem) {
await sendQuote(cssaGuild, {
message: row.quote,
timestamp: new Date(),
quotee: row.author,
});
}
40 changes: 26 additions & 14 deletions src/nocodb-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
interface NocoDBBaseWebhook {
id: string;
type: unknown;
data: {
table_id: string;
table_name: string;
Expand All @@ -8,48 +9,51 @@ interface NocoDBBaseWebhook {
};
}

interface NocoDBCreatedWebhook extends NocoDBBaseWebhook {
interface NocoDBCreatedWebhook<ItemType extends DBItem>
extends NocoDBBaseWebhook {
type: "records.after.insert";
data: {
table_id: string;
table_name: string;
view_id: string;
view_name: string;
rows: DBItem[];
rows: ItemType[];
};
}

interface NocoDBUpdatedWebhook extends NocoDBBaseWebhook {
interface NocoDBUpdatedWebhook<ItemType extends DBItem>
extends NocoDBBaseWebhook {
type: "records.after.update";
data: {
table_id: string;
table_name: string;
view_id: string;
view_name: string;
previous_rows: DBItem[];
rows: DBItem[];
previous_rows: ItemType[];
rows: ItemType[];
};
}

interface NocoDBDeletedWebhook extends NocoDBBaseWebhook {
interface NocoDBDeletedWebhook<ItemType extends DBItem>
extends NocoDBBaseWebhook {
type: "rows.after.delete";
data: {
table_id: string;
table_name: string;
view_id: string;
view_name: string;
previous_rows: DBItem[];
rows: DBItem[];
previous_rows: ItemType[];
rows: ItemType[];
};
}

export type NocoDBWebhook =
| NocoDBCreatedWebhook
| NocoDBUpdatedWebhook
| NocoDBDeletedWebhook;
export type NocoDBWebhook<ItemType extends DBItem> =
| NocoDBCreatedWebhook<ItemType>
| NocoDBUpdatedWebhook<ItemType>
| NocoDBDeletedWebhook<ItemType>;

export interface DBGetRowsResponse {
list: [DBItem];
export interface DBGetRowsResponse<ItemType extends DBItem> {
list: [ItemType];
PageInfo: [DBPageInfo];
}

Expand All @@ -63,8 +67,16 @@ export interface DBPageInfo {

export interface DBItem {
id: number;
}

export interface MembershipDBItem extends DBItem {
discord_username: string;
life_member: boolean;
committee: boolean;
cro: boolean;
}

export interface QuoteDBItem extends DBItem {
quote: string;
author?: string;
}

0 comments on commit b4300d3

Please sign in to comment.