Skip to content

Commit

Permalink
feat: join hubs through /hub browse (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
dev-737 authored Sep 16, 2023
1 parent bc047f6 commit 495a4c9
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 94 deletions.
190 changes: 163 additions & 27 deletions src/Scripts/hub/browse.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, ChatInputCommandInteraction, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js';
import {
ActionRowBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
ChannelSelectMenuBuilder,
ChannelType,
ChatInputCommandInteraction,
EmbedBuilder,
GuildTextBasedChannel,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
} from 'discord.js';
import { calculateAverageRating, createHubListingsEmbed, getDb } from '../../Utils/functions/utils';
import { paginate } from '../../Utils/functions/paginator';
import { hubs } from '@prisma/client';
import logger from '../../Utils/logger';
import { captureException } from '@sentry/node';
import createConnection from '../network/createConnection';

export async function execute(interaction: ChatInputCommandInteraction) {
const sortBy = interaction.options.getString('sort') as 'connections' | 'active' | 'popular' | 'recent' | undefined;
const sortBy = interaction.options.getString('sort') as
| 'connections'
| 'active'
| 'popular'
| 'recent'
| undefined;
const hubName = interaction.options.getString('search') || undefined;

const db = getDb();
let sortedHubs: hubs[] = [];


switch (sortBy) {
case 'active':
sortedHubs = await db.hubs.findMany({
where: { name: hubName, private: false },
orderBy: { messages: { _count: 'desc' } },
});
break;
case 'popular':
sortedHubs = (await db.hubs
.findMany({ where: { name: hubName, private: false } }))
.sort((a, b) => {
const aAverage = calculateAverageRating(a.rating.map((rating) => rating.rating));
const bAverage = calculateAverageRating(b.rating.map((rating) => rating.rating));
return bAverage - aAverage;
});
sortedHubs = (
await db.hubs.findMany({
where: { name: hubName, private: false },
include: { connections: true },
})
).sort((a, b) => {
const aAverage = calculateAverageRating(a.rating.map((rating) => rating.rating));
const bAverage = calculateAverageRating(b.rating.map((rating) => rating.rating));
return bAverage - aAverage;
});
break;
case 'recent':
sortedHubs = await db.hubs.findMany({
Expand All @@ -41,19 +56,25 @@ export async function execute(interaction: ChatInputCommandInteraction) {
orderBy: { connections: { _count: 'desc' } },
});
break;

case 'active':
default:
sortedHubs = await db.hubs.findMany({ where: { name: hubName, private: false } });
sortedHubs = await db.hubs.findMany({
where: { name: hubName, private: false },
orderBy: { messages: { _count: 'desc' } },
});
break;
}

const hubList = await Promise.all(
sortedHubs?.map(async (hub) => {
const totalNetworks = await db.connectedList
.count({ where: { hubId: hub.id } })
.catch(() => 0);

const hubList = sortedHubs?.map(async (hub) => {
const totalNetworks = await db.connectedList
.count({ where: { hubId: hub.id } })
.catch(() => 0);

return createHubListingsEmbed(hub, { totalNetworks });
});
return createHubListingsEmbed(hub, { totalNetworks });
}),
);

if (!hubList || hubList.length === 0) {
interaction.reply({
Expand All @@ -68,14 +89,18 @@ export async function execute(interaction: ChatInputCommandInteraction) {
.setCustomId(`rate-${sortedHubs[0].id}`)
.setLabel('Rate')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId(`join-${sortedHubs[0].id}`)
.setLabel('Join')
.setStyle(ButtonStyle.Success),
);


paginate(interaction, await Promise.all(hubList), {
paginate(interaction, hubList, {
extraComponent: {
actionRow: [paginateBtns],
updateComponents(pageNumber) {
paginateBtns.components[0].setCustomId(`rate-${sortedHubs[pageNumber].id}`);
paginateBtns.components[1].setCustomId(`join-${sortedHubs[pageNumber].id}`);
return paginateBtns;
},
async execute(i: ButtonInteraction) {
Expand All @@ -97,7 +122,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
);
await i.showModal(ratingModal);
i.awaitModalSubmit({ time: 30_000 })
.then(async m => {
.then(async (m) => {
const rating = parseInt(m.fields.getTextInputValue('rating'));
if (isNaN(rating) || rating < 1 || rating > 5) {
return m.reply({
Expand Down Expand Up @@ -142,6 +167,117 @@ export async function execute(interaction: ChatInputCommandInteraction) {
}
});
}
else if (i.customId.startsWith('join-')) {
const hubDetails = await db.hubs.findFirst({
where: { id: i.customId.replace('join-', '') },
include: { connections: true },
});

if (!hubDetails) {
i.reply({
content: 'Hub not found.',
ephemeral: true,
});
return;
}

const alreadyJoined = hubDetails.connections.find((c) => c.serverId === i.guildId);
if (alreadyJoined) {
i.reply({
content: `You have already joined **${hubDetails.name}** from <#${alreadyJoined.channelId}>!`,
ephemeral: true,
});
return;
}

let channel = i.channel;

const channelSelect = new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
new ChannelSelectMenuBuilder()
.setCustomId('channel_select')
.setPlaceholder('Select a different channel.')
.setChannelTypes([
ChannelType.PublicThread,
ChannelType.PrivateThread,
ChannelType.GuildText,
]),
);

const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('confirm')
.setLabel('Confirm')
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId('cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Danger),
);

// use current channel embed
const embed = new EmbedBuilder()
.setDescription(`
Are you sure you wish to join **${hubDetails.name}** from ${interaction.channel}?
**Note:** You can always change this later using \`/network manage\`.
`,
)
.setColor('Aqua')
.setFooter({ text: 'Use a different channel? Use the dropdown below.' });

const reply = await i.reply({
embeds: [embed],
components: [channelSelect, buttons],
fetchReply: true,
ephemeral: true,
});

const response = await reply
.awaitMessageComponent({
time: 60_000 * 2,
filter: (e) => e.user.id === i.user.id,
})
.catch(() => null);

if (!response?.inCachedGuild() || response.customId === 'cancel') {
i.deleteReply().catch(() => null);
return;
}

if (response.isChannelSelectMenu()) {
channel = response.guild.channels.cache.get(response.values[0]) as GuildTextBasedChannel;
}

if (
(channel?.type === ChannelType.GuildText || channel?.isThread()) &&
(response.customId === 'confirm' || response.customId === 'channel_select')
) {
const channelConnected = await db.connectedList.findFirst({
where: { channelId: channel.id },
});

if (channelConnected) {
response.update({
content: 'This channel is already connected to another hub!',
embeds: [],
components: [],
});
return;
}

createConnection.execute(response, hubDetails, channel, true).then((success) => {
if (success) {
response.editReply({
content: `Successfully joined hub ${hubDetails.name} from ${channel}! Use \`/network manage\` to manage your connection. And \`/hub leave\` to leave the hub.`,
embeds: [],
components: [],
});
return;
}
response.message.delete().catch(() => null);
});
}
}
},
},
});
Expand Down
File renamed without changes.
62 changes: 28 additions & 34 deletions src/Scripts/network/createConnection.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,22 @@
import { stripIndents } from 'common-tags';
import { ChannelType, ChatInputCommandInteraction, Collection, TextChannel, ThreadChannel } from 'discord.js';
import { ChannelType, AnySelectMenuInteraction, ChatInputCommandInteraction, TextChannel, ThreadChannel, ButtonInteraction } from 'discord.js';
import { disconnect } from '../../Structures/network';
import { hubs } from '@prisma/client';
import logger from '../../Utils/logger';
import onboarding from './onboarding';
import { getDb } from '../../Utils/functions/utils';

const onboardingInProgress = new Collection<string, string>();

export default {
async execute(interaction: ChatInputCommandInteraction, hub: hubs, networkChannel: TextChannel | ThreadChannel) {
async execute(
interaction: AnySelectMenuInteraction | ButtonInteraction | ChatInputCommandInteraction,
hub: hubs,
networkChannel: TextChannel | ThreadChannel,
ephemeral = false,
) {
const emoji = interaction.client.emotes.normal;

// Check if server is already attempting to join a hub
if (onboardingInProgress.has(networkChannel.id)) {
const err = {
content: `${emoji.no} There has already been an attempt to join a hub in ${networkChannel}. Please wait for that to finish before trying again!`,
ephemeral: true,
};
interaction.deferred || interaction.replied
? interaction.followUp(err)
: interaction.reply(err);
return;
}
// Mark this as in-progress so server can't join twice
onboardingInProgress.set(networkChannel.id, networkChannel.id);

// Show new users rules & info about network
const onboardingStatus = await onboarding.execute(interaction, hub.name);
// remove in-progress marker as onboarding has either been cancelled or completed
onboardingInProgress.delete(networkChannel.id);
const onboardingStatus = await onboarding.execute(interaction, hub.name, networkChannel.id, ephemeral);
// if user cancelled onboarding or didn't click any buttons, stop here
if (!onboardingStatus) return;

Expand All @@ -38,7 +25,7 @@ export default {
let webhook;
if (networkChannel.isThread() && networkChannel.parent) {
const webhooks = await networkChannel.parent.fetchWebhooks();
const webhookCreated = webhooks.find(w => w.owner?.id === interaction.client.user?.id);
const webhookCreated = webhooks.find((w) => w.owner?.id === interaction.client.user?.id);

if (webhookCreated) {
webhook = webhookCreated;
Expand All @@ -57,13 +44,14 @@ export default {
});
}
else {
return interaction.followUp('This channel is not supported for InterChat. Please use a text channel or a thread.');
return interaction.followUp(
'This channel is not supported for InterChat. Please use a text channel or a thread.',
);
}


const { connectedList } = getDb();
createdConnection = await connectedList.create({
data:{
data: {
channelId: networkChannel.id,
parentId: networkChannel.isThread() ? networkChannel.id : undefined,
serverId: networkChannel.guild.id,
Expand All @@ -76,14 +64,18 @@ export default {
});

const numOfConnections = await connectedList.count({ where: { hub: { id: hub.id } } });
await networkChannel?.send(`This channel has been connected with **${hub.name}**. ${
numOfConnections > 1
? `You are currently with ${numOfConnections - 1} other servers, Enjoy! ${emoji.clipart}`
: `It seems no one else is there currently... *cricket noises* ${emoji.clipart}`
}`);
await networkChannel?.send(
`This channel has been connected with **${hub.name}**. ${
numOfConnections > 1
? `You are currently with ${numOfConnections - 1} other servers, Enjoy! ${
emoji.clipart
}`
: `It seems no one else is there currently... *cricket noises* ${emoji.clipart}`
}`,
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
catch (err: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
logger.error(err);
if (err.message === 'Missing Permissions' || err.message === 'Missing Access') {
const errMsg = `${emoji.no} Please make sure you have granted me \`Manage Webhooks\` and \`View Channel\` permissions for the selected channel.`;
Expand All @@ -97,17 +89,19 @@ export default {
? interaction.followUp(errMsg)
: interaction.reply(errMsg);
}
onboardingInProgress.delete(networkChannel.id);
disconnect(networkChannel.id);
return;
}

interaction.client.sendInNetwork(stripIndents`
interaction.client.sendInNetwork(
stripIndents`
A new server has joined us! ${emoji.clipart}
**Server Name:** __${interaction.guild?.name}__
**Member Count:** __${interaction.guild?.memberCount}__
`, { id: hub.id });
`,
{ id: hub.id },
);

// return the created connection so we can use it in the next step
return createdConnection;
Expand Down
Loading

0 comments on commit 495a4c9

Please sign in to comment.