all repos — stealth-developers @ a19b9c8aa54b7c47b8677c31a7d567bd24c632ee

apps/bot/src/feats/link/index.ts (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
import { Result } from "@sapphire/result";
import { editActor, getActor } from "@stealth-developers/db";
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";

import { logger } from "@/lib";
import { useGetData } from "@/middleware";

import { client } from "../roblox/client";

export const syncCommand = useGetData.command("sync", {
	description: "Link your Roblox account to your Discord account",
});

syncCommand.subcommand("pair", {
	description: "Link your Roblox account to your Discord account",
	run: async (interaction, _, { actor }) => {
		if (!actor)
			return interaction.reply({ content: "Failed to fetch your data.", ephemeral: true });

		const pin = Math.floor(100000 + Math.random() * 900000);
		const pinExpiryMs = 5 * 60 * 1000;

		await editActor(actor.id, {
			pin: String(pin),
			pinExpiresIn: new Date(Date.now() + pinExpiryMs),
		});

		interaction.reply({
			content: [
				`Your pin is: **${pin}**.`,
				"Input this pin in Ground War or Warfare Tycoon to link your account.",
			].join(" "),
			flags: ["Ephemeral"],
		});

		const pollIntervalMs = 5000;
		const startTime = Date.now();

		const interval = setInterval(async () => {
			try {
				if (Date.now() - startTime > pinExpiryMs) {
					clearInterval(interval);
					await interaction
						.editReply({
							content:
								"Your verification pin has expired. Please run the command again to generate a new one.",
						})
						.catch(() => {});
					return;
				}

				const updatedActor = await getActor(actor.id);

				if (updatedActor && updatedActor.robloxId) {
					clearInterval(interval);

					const oauthButton = new ButtonBuilder()
						.setLabel("Finalize Profile Sync")
						.setStyle(ButtonStyle.Link)
						.setURL(`http://127.0.0.1:3000/auth/login?actorId=${actor.id}`);

					const row = new ActionRowBuilder<ButtonBuilder>().addComponents(oauthButton);

					await interaction
						.editReply({
							content:
								"Successfully linked your Roblox account! Click the button below to authorize game widgets on your Discord profile.",
							components: [row],
						})
						.catch(() => {});
				}
			} catch (error) {
				logger.error(error, "Error during pin verification polling");
			}
		}, pollIntervalMs);
	},
});

syncCommand.subcommand("unlink", {
	description: "Unlink your Roblox account from your Discord account",
	run: async (interaction, _, { actor }) => {
		if (!actor)
			return interaction.reply({ content: "Failed to fetch your data.", flags: ["Ephemeral"] });

		await editActor(actor.id, {
			pin: null,
			pinExpiresIn: null,
			robloxId: null,
		});

		interaction.reply({ content: "Your account has been unlinked.", flags: ["Ephemeral"] });
	},
});

syncCommand.subcommand("status", {
	description: "Check your linked Roblox account",
	run: async (interaction, _, { actor }) => {
		if (!actor)
			return interaction.reply({ content: "Failed to fetch your data.", flags: ["Ephemeral"] });

		if (!actor.robloxId)
			return interaction.reply({
				content: "You are not linked to a Roblox account.",
				flags: ["Ephemeral"],
			});

		const accountRes = await Result.fromAsync(client.getUserProfile(actor.robloxId));

		if (accountRes.isErr())
			return interaction.reply({
				content: "Failed to fetch your Roblox account.",
				flags: ["Ephemeral"],
			});

		const account = accountRes.unwrap();

		interaction.reply({
			content: `You are linked to Roblox account **@${account.name}**.`,
			flags: ["Ephemeral"],
		});
	},
});