From 89940acb96fa0eec24c197a3df2b1a864d8859e2 Mon Sep 17 00:00:00 2001 From: katsumi143 <32640219+katsumi143@users.noreply.github.com> Date: Thu, 16 Nov 2023 00:38:53 +1000 Subject: [PATCH] add onboarding --- src/commands/general/whois.ts | 11 ++-- src/commands/mod.ts | 2 +- src/commands/{roblox => syncing}/forcesync.ts | 0 .../{roblox => syncing}/forcesyncall.ts | 16 ++++-- src/commands/{roblox => syncing}/mod.ts | 0 src/commands/{roblox => syncing}/sync.ts | 56 +++++++------------ src/constants.ts | 15 +++++ src/localisation/locales/en-US/command.json | 7 ++- src/routes/signup-finish.ts | 7 ++- 9 files changed, 64 insertions(+), 50 deletions(-) rename src/commands/{roblox => syncing}/forcesync.ts (100%) rename src/commands/{roblox => syncing}/forcesyncall.ts (87%) rename src/commands/{roblox => syncing}/mod.ts (100%) rename src/commands/{roblox => syncing}/sync.ts (76%) create mode 100644 src/constants.ts diff --git a/src/commands/general/whois.ts b/src/commands/general/whois.ts index 6a7af2d..5633b38 100644 --- a/src/commands/general/whois.ts +++ b/src/commands/general/whois.ts @@ -23,12 +23,15 @@ export default command(({ t, token, guild_id }, { target }) => defer(token, asyn value: roles.join(' • ') }); - const robloxLinks = await supabase.from('user_connections') - .select('sub') + const robloxLinks = await supabase.from('mellow_user_server_connections') + .select('connection:user_connections ( sub )') .eq('user_id', user.id) - .eq('type', 2); + .eq('server_id', guild_id) + .eq('user_connections.type', 2); if (robloxLinks.data) { - const users = await ROBLOX_API.users.getProfiles(robloxLinks.data.map(i => i.sub), ['names.username', 'names.combinedName']); + const users = await ROBLOX_API.users.getProfiles(robloxLinks.data.map(i => i.connection.sub), ['names.username', 'names.combinedName']); fields.push({ name: t('whois.roblox'), value: users.map(user => t('whois.roblox.user', [user])).join(' • ') diff --git a/src/commands/mod.ts b/src/commands/mod.ts index cae247c..0eec097 100644 --- a/src/commands/mod.ts +++ b/src/commands/mod.ts @@ -9,7 +9,7 @@ import type { DiscordInteraction, CommandExecutePayload, DiscordApplicationComma import { setup } from './other/mod.ts'; import { ping, roll, whois, pokemon } from './general/mod.ts'; -import { sync, forcesync, forcesyncall } from './roblox/mod.ts'; +import { sync, forcesync, forcesyncall } from './syncing/mod.ts'; export const commands: Record> = { ping, roll, diff --git a/src/commands/roblox/forcesync.ts b/src/commands/syncing/forcesync.ts similarity index 100% rename from src/commands/roblox/forcesync.ts rename to src/commands/syncing/forcesync.ts diff --git a/src/commands/roblox/forcesyncall.ts b/src/commands/syncing/forcesyncall.ts similarity index 87% rename from src/commands/roblox/forcesyncall.ts rename to src/commands/syncing/forcesyncall.ts index fe106f9..c65ac63 100644 --- a/src/commands/roblox/forcesyncall.ts +++ b/src/commands/syncing/forcesyncall.ts @@ -7,9 +7,9 @@ import { defer, content } from '../response.ts'; import { DISCORD_APP_ID } from '../../util/constants.ts'; import { sendSyncedWebhookEvent } from '../../syncing.ts'; import type { Log, RobloxProfile, WebhookSyncedEventItem } from '../../types.ts'; -import { DiscordMessageFlag, MellowServerLogType, WebhookSyncedEventItemState } from '../../enums.ts'; import { supabase, getServer, getUsersByDiscordId, getServerProfileSyncingActions } from '../../database.ts'; import { getDiscordServer, getServerMembers, getMemberPosition, editOriginalResponse } from '../../discord.ts'; +import { DiscordMessageFlag, UserConnectionType, MellowServerLogType, WebhookSyncedEventItemState } from '../../enums.ts'; export default command(({ t, token, member, guild_id }) => defer(token, async () => { const server = await getServer(guild_id); if (!server) @@ -32,13 +32,21 @@ export default command(({ t, token, member, guild_id }) => defer(token, async () const users = await getUsersByDiscordId(members.map(member => member.user.id)); const links = await supabase.from('users') - .select('user_connections ( sub, type, user_id )') + .select('connections:mellow_user_server_connections ( connection:user_connections ( sub, type, user_id ) )') .in('id', users.map(user => user.id)) - .eq('user_connections.type', 2) + .eq('mellow_user_server_connections.user_connections.type', 2) .then(response => { if (response.error) console.error(response.error); - return response.data?.map(item => item.user_connections[0]).filter(i => i) ?? []; + return response.data?.map(item => item.connections[0]?.connection).filter(i => i) ?? []; }); const robloxUsers: RobloxProfile[] = await ROBLOX_API.users.getProfiles(links.map(link => link.sub as string), ['names.username', 'names.combinedName']); diff --git a/src/commands/roblox/mod.ts b/src/commands/syncing/mod.ts similarity index 100% rename from src/commands/roblox/mod.ts rename to src/commands/syncing/mod.ts diff --git a/src/commands/roblox/sync.ts b/src/commands/syncing/sync.ts similarity index 76% rename from src/commands/roblox/sync.ts rename to src/commands/syncing/sync.ts index 716d8cd..a473361 100644 --- a/src/commands/roblox/sync.ts +++ b/src/commands/syncing/sync.ts @@ -5,6 +5,7 @@ import { sendLogs } from '../../logging.ts'; import { syncMember } from '../../roblox.ts'; import { defer, content } from '../response.ts'; import { sendSyncedWebhookEvent } from '../../syncing.ts'; +import { MELLOW_SYNC_REQUIREMENT_CONNECTIONS } from '../../constants.ts'; import { getDiscordServer, getMemberPosition, editOriginalResponse } from '../../discord.ts'; import type { User, TranslateFn, MellowServer, DiscordMember, RobloxProfile } from '../../types.ts'; import { supabase, getServer, getUserByDiscordId, getServerProfileSyncingActions } from '../../database.ts'; @@ -29,47 +30,33 @@ export default command(({ t, token, locale, member, guild_id }) => defer(token, }); return editOriginalResponse(token, { flags: DiscordMessageFlag.Ephemeral, - content: t('sync.signup'), - components: [{ - type: 1, - components: [{ - url: 'https://discord.com/api/oauth2/authorize?client_id=1068554282481229885&redirect_uri=https%3A%2F%2Fapi.hakumi.cafe%2Fv1%2Fauth%2Fcallback%2F0&response_type=code&scope=identify&state=roblox', - type: 2, - style: 5, - label: t('common:action.continue') - }] - }] + content: t('sync.signup', ['TODO', guild_id]) }); }, DiscordMessageFlag.Ephemeral)); -export async function verify(t: TranslateFn, executor: DiscordMember | null, server: MellowServer, token: string, serverId: string, user: User | undefined, member: DiscordMember, syncAs?: number) { - const userBind = user ? await supabase.from('users') - .select('user_connections ( sub )') +export async function verify(t: TranslateFn, executor: DiscordMember | null, server: MellowServer, token: string, serverId: string, user: User | undefined, member: DiscordMember) { + const userConnections = user ? await supabase.from('users') + .select('connections:mellow_user_server_connections ( connection:user_connections ( sub, type ) )') .eq('id', user.id) - .eq('user_connections.type', 2) .limit(1) .maybeSingle() .then(response => { if (response.error) console.error(response.error); - return response.data?.user_connections[0]; + if (!response.data) + return []; + return response.data?.connections.map(item => item.connection); }) - : null; - if (!userBind) - return editOriginalResponse(token, { - content: t('sync.signup'), - components: [{ - type: 1, - components: [{ - url: 'https://discord.com/api/oauth2/authorize?client_id=1068554282481229885&redirect_uri=https%3A%2F%2Fapi.hakumi.cafe%2Fv1%2Fauth%2Fcallback%2F0&response_type=code&scope=identify&state=roblox', - type: 2, - style: 5, - label: t('common:action.continue') - }] - }] - }); + : []; - const robloxId = syncAs ?? userBind?.sub as string; + const robloxId = userConnections?.find(item => item.type === UserConnectionType.Roblox)?.sub; const [ruser]: RobloxProfile[] = robloxId ? await ROBLOX_API.users.getProfiles([robloxId], ['names.username', 'names.combinedName']) : [undefined]; const serverLinks = await getServerProfileSyncingActions(serverId); const discordServer = (await getDiscordServer(serverId))!; @@ -99,16 +86,15 @@ export async function verify(t: TranslateFn, executor: DiscordMember | null, ser role_changes: roleChanges, discord_member_id: member.user.id, discord_server_id: serverId, - relevant_user_connections: [{ - sub: robloxId.toString(), - type: UserConnectionType.Roblox - }] + relevant_user_connections: userConnections }]); } const removed = banned || kicked; const addedRoles = roleChanges.filter(item => item.type === RoleChangeType.Added); const removedRoles = roleChanges.filter(item => item.type === RoleChangeType.Removed); + + const actionConnections = [...new Set(serverLinks.map(item => item.requirements.map(item => MELLOW_SYNC_REQUIREMENT_CONNECTIONS[item.type]!).filter(i => i)).flat())]; return editOriginalResponse(token, { embeds: profileChanged ? [{ fields: [ @@ -124,7 +110,7 @@ export async function verify(t: TranslateFn, executor: DiscordMember | null, ser }] : [] ] }] : [], - content: removed ? t('sync.complete.removed') + t(`sync.complete.removed.${banned ? 0 : 1}`) : t(`sync.complete.${profileChanged}`) + (roleChanges.length ? nicknameChanged ? t('sync.complete.true.2') : t('sync.complete.true.0') : nicknameChanged ? t('sync.complete.true.1') : ''), + content: removed ? t('sync.complete.removed') + t(`sync.complete.removed.${banned ? 0 : 1}`) : t(`sync.complete.${profileChanged}`) + (roleChanges.length ? nicknameChanged ? t('sync.complete.true.2') : t('sync.complete.true.0') : nicknameChanged ? t('sync.complete.true.1') : '') + (userConnections.length >= actionConnections.length ? '' : t('sync.complete.missing_connections', [serverId])), components: [] }); } \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..30c77e2 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,15 @@ +import { UserConnectionType, MellowProfileSyncActionRequirementType } from './enums.ts'; +export const MELLOW_SYNC_REQUIREMENT_CONNECTIONS: Record = { + [MellowProfileSyncActionRequirementType.RobloxHaveConnection]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.RobloxHaveGroupRole]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.RobloxHaveGroupRankInRange]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.RobloxInGroup]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.RobloxBeFriendsWith]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.MeetOtherAction]: null, + [MellowProfileSyncActionRequirementType.HAKUMIInTeam]: null, + [MellowProfileSyncActionRequirementType.SteamInGroup]: null, + [MellowProfileSyncActionRequirementType.RobloxHaveAsset]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.RobloxHaveBadge]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.RobloxHavePass]: UserConnectionType.Roblox, + [MellowProfileSyncActionRequirementType.GitHubInOrganisation]: UserConnectionType.GitHub +}; \ No newline at end of file diff --git a/src/localisation/locales/en-US/command.json b/src/localisation/locales/en-US/command.json index ea73b12..524c075 100644 --- a/src/localisation/locales/en-US/command.json +++ b/src/localisation/locales/en-US/command.json @@ -13,8 +13,8 @@ "pokemon.embed.summary": "it is a {{0.types.0.type.name}} type with {{0.abilities.length}} abilities", "sync": "sync", - "sync.summary": "Sync your Server Profile with your Roblox Account.", - "sync.signup": "## Connect Roblox Account\nYou must be new to mellow, click Continue to connect your Roblox Account.\n~~This message will update with your server profile status after you link a Roblox Account.~~\nThe functionality above is currently under maintenance, please execute this command again upon completion.", + "sync.summary": "Sync your server profile.", + "sync.signup": "## Hello, welcome to the server!\nYou appear to be new to mellow, this server uses mellow to sync member profiles with external services, such as Roblox.\nIf you would like to continue, please continue [here](https://discord.com/api/oauth2/authorize?client_id=1068554282481229885&redirect_uri=https%3A%2F%2Fapi.hakumi.cafe%2Fv1%2Fauth%2Fcallback%2F0&response_type=code&scope=identify&state=mlw{{1}}mlw), don't worry, it shouldn't take long!", "sync.complete.true": "## Server Profile Updated\n", "sync.complete.true.0": "Your roles have been updated.", "sync.complete.true.1": "Your nickname has been updated.", @@ -26,6 +26,7 @@ "sync.complete.removed2": "has been removed from this server", "sync.complete.removed.0": "This member has been banned for meeting the requirements of a ban action.", "sync.complete.removed.1": "This member has been kicked for meeting the requirements of a kick action.", + "sync.complete.missing_connections": "\n\n### You're missing connections\nYou haven't given this server access to all connections yet, change that [here](https://hakumi.cafe/mellow/server/{{0}}/onboarding)!", "sync.no_server": "## Cannot Sync Profile\nThis server has not been set-up with mellow yet, ", "forcesync": "forcesync", @@ -42,7 +43,7 @@ "setup.summary": "Connect this Discord Server to mellow.", "setup.signup": "## Account required\nBefore you can continue, you need to [sign-up](https://hakumi.cafe/sign-up) for a HAKUMI Account.\nAfter that, if you did not sign-up with Discord, please connect your account [here](https://hakumi.cafe/settings/account/connections).\nExecute this command again after completing these instructions.", "setup.exists": "## Server already connected\nThis server is already connected to mellow, view it [here](https://hakumi.cafe/mellow/server/{{0}}/settings).", - "setup.done": "## Server connected\nThis server is now connected to mellow!\nConfigure it online [here](https://hakumi.cafe/mellow/server/{{0}}/settings).", + "setup.done": "## Server connected\nThis server is now connected to mellow!\nConfigure it online [here](https://hakumi.cafe/mellow/server/{{0}}/settings).\n\nIf you haven't already, and you plan for me to manage members' roles, you may need to position one of my roles at very top of your server.", "whois": "profile", "whois.summary": "Request someone's HAKUMI Profile.", diff --git a/src/routes/signup-finish.ts b/src/routes/signup-finish.ts index 24bd939..550beb6 100644 --- a/src/routes/signup-finish.ts +++ b/src/routes/signup-finish.ts @@ -1,6 +1,6 @@ import { getFixedT } from 'i18next'; -import { verify } from '../commands/roblox/sync.ts'; +import { verify } from '../commands/syncing/sync.ts'; import { MELLOW_KEY } from '../util/constants.ts'; import { getServerMember } from '../discord.ts'; import { getUser, supabase, getServer } from '../database.ts'; @@ -25,9 +25,10 @@ export default async (request: Request) => { const t = getFixedT(data.locale, 'command'); const server = await getServer(data.server_id); - await verify(t, null, server!, data.interaction_token, data.server_id, user, member); + await verify(t, null, server!, data.interaction_token, data.server_id, user, member) + .catch(console.error); await supabase.from('mellow_signups').delete().eq('user_id', discordId); - return new Response('great!', { status: 200 }); + return new Response(); } \ No newline at end of file