Skip to content

Commit

Permalink
✨ [Discord] Broaden support for URL variations
Browse files Browse the repository at this point in the history
  • Loading branch information
beefchimi committed Mar 1, 2022
1 parent d77adf9 commit 8d8be8f
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 36 deletions.
10 changes: 10 additions & 0 deletions src/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,13 @@ export const defaultUserMatcher = {
subdomain: /[^.]+/,
path: /[^/]+/,
};

// TODO: This should probably be re-located elsewhere
// https://github.com/beefchimi/socialite/issues/35
export const discordPreferredUrls = {
users: `https://discordapp.com/users/${profileReplacement.user}`,
channels: `https://discord.com/channels/${profileReplacement.user}`,
vanity: `https://discord.gg/${profileReplacement.user}`,
// TODO: This result should not be supported
default: `https://discord.com/${profileReplacement.user}`,
};
21 changes: 18 additions & 3 deletions src/networks/discord.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import {profileReplacement} from '../capture';
import {discordPreferredUrls} from '../capture';
import type {SocialiteNetwork} from '../types';

// Discord is difficult to solve given Socialite's current design.
// There are essentially 3 different urls to support:
// 1. User profiles (discordapp.com/users/*)
// 2. Server/channel urls (discord.com/channels/{serverid}/{channelid})
// 3. Official vanity urls (discord.gg/*)
// Since we are not yet validating against a Top-level domain (.gg),
// any `discord` url validates as true and captures the `path`.
// This degrades the confidence provided by `users` or `channels`.

// TODO: Solve this problem by improving `preferredUrl` and parsing criteria.
// https://github.com/beefchimi/socialite/issues/35
export const discord: SocialiteNetwork = {
id: 'discord',
preferredUrl: `https://discordapp.com/users/${profileReplacement.user}`,
preferredUrl: discordPreferredUrls.users,
matcher: {
domain: /discord/,
user: /^(?:\/users\/)([^/]+)/,
user: /^\/(?:users\/|channels\/)?([^/]+)/,
// TODO: If we want to support capturing EVERYTHING after
// the first `/` (necessary for capturing the `channelid`),
// then we would need to use the following:
// user: /^\/(?:users\/|channels\/)?(.+)/,
},
};
120 changes: 98 additions & 22 deletions src/networks/tests/discord.test.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,114 @@
import {Socialite} from '../../socialite';
import type {SocialiteProfile} from '../../types';
import {allSocialiteNetworks, mockGenericUser} from '../../tests/fixtures';
import {
allSocialiteNetworks,
discordValidUrls,
mockGenericUser,
} from '../../tests/fixtures';
import {discord} from '../discord';

describe('Social networks > discord', () => {
const mockSocialite = new Socialite(allSocialiteNetworks);
const mockCommonUrl = `https://www.discordapp.com/users/${mockGenericUser}`;

it('returns expected `id` and `user` from common url', () => {
const {id, user} = mockSocialite.parseProfile(
mockCommonUrl,
) as SocialiteProfile;
// TODO: We want to resolve these in the future
// https://github.com/beefchimi/socialite/issues/35
describe('bogus', () => {
it('mistakenly returns the first path match for any `discord` url', () => {
const mockPageSlug = 'about-us-page';
const mockBogusUrl = `https://www.discord.com/${mockPageSlug}`;

expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
const {id, user} = mockSocialite.parseProfile(
mockBogusUrl,
) as SocialiteProfile;

expect(id).toBe(discord.id);
expect(user).toBe(mockPageSlug);
});
});

describe('users', () => {
const mockUsersUrl = `https://www.discordapp.com/users/${mockGenericUser}`;

it('returns `id` and `user`', () => {
const {id, user} = mockSocialite.parseProfile(
mockUsersUrl,
) as SocialiteProfile;

expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
});

it('omits any trailing path after the first `user` match', () => {
const mockUsersTrailingUrl = `${mockUsersUrl}/trail-123`;

const {id, user} = mockSocialite.parseProfile(
mockUsersTrailingUrl,
) as SocialiteProfile;

expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
});
});

it('returns expected `id` and `user` from url with trailing path', () => {
const mockUncommonUrl = `${mockCommonUrl}/trail-123`;
const {id, user} = mockSocialite.parseProfile(
mockUncommonUrl,
) as SocialiteProfile;
describe('channels', () => {
const mockChannelsUrl = `https://www.discord.com/channels/${mockGenericUser}`;

it('returns `id` and `user`', () => {
const {id, user} = mockSocialite.parseProfile(
mockChannelsUrl,
) as SocialiteProfile;

expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
});

it('omits any trailing path after the first `user` match', () => {
const mockChannelsTrailingUrl = `${mockChannelsUrl}/trail-123`;

const {id, user} = mockSocialite.parseProfile(
mockChannelsTrailingUrl,
) as SocialiteProfile;

expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
});
});

describe('vanity', () => {
const mockVanityUrl = `https://discord.gg/${mockGenericUser}`;

it('returns `id` and `user`', () => {
const {id, user} = mockSocialite.parseProfile(
mockVanityUrl,
) as SocialiteProfile;

expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
});

it('omits any trailing path after the first `user` match', () => {
const mockVanityTrailingUrl = `${mockVanityUrl}/trail-123`;

const {id, user} = mockSocialite.parseProfile(
mockVanityTrailingUrl,
) as SocialiteProfile;

expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
expect(id).toBe(discord.id);
expect(user).toBe(mockGenericUser);
});
});

it('returns `id` with no `user` when provided an unrecognized leading path', () => {
const mockUnsupportedUrl = `https://discordapp.com/foo/${mockGenericUser}`;
const match = mockSocialite.parseProfile(
mockUnsupportedUrl,
) as SocialiteProfile;
describe('all variations', () => {
it('returns `id` and `user`', () => {
discordValidUrls.forEach(({originalUrl, preferredUrl, user}) => {
const match = mockSocialite.parseProfile(
originalUrl,
) as SocialiteProfile;

expect(match.id).toBe(discord.id);
expect(match.user).toBeUndefined();
expect(match.id).toBe(discord.id);
expect(match.preferredUrl).toBe(preferredUrl);
expect(match.user).toBe(user);
});
});
});
});
14 changes: 9 additions & 5 deletions src/socialite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {defaultUserMatcher, schemeRegExp} from './capture';
import {MatchUserSource} from './types';
import type {
BasicUrl,
DiscordProfile,
NetworkId,
NetworkMap,
ParsedUrlGroups,
Expand All @@ -15,6 +16,7 @@ import type {
} from './types';
import {
filterNetworkProperties,
getDiscordPreferredUrl,
getUrlGroups,
getUrlWithSubstitutions,
} from './utilities';
Expand Down Expand Up @@ -155,11 +157,13 @@ export class Socialite {
return minResult;
}

const preferredUrl = getUrlWithSubstitutions(
targetNetwork.preferredUrl,
user,
prefix,
);
// TODO: Resolve this special condition
// https://github.com/beefchimi/socialite/issues/35
const preferredUrl =
targetNetwork.id === 'discord'
? getDiscordPreferredUrl({...minResult, user} as DiscordProfile)
: getUrlWithSubstitutions(targetNetwork.preferredUrl, user, prefix);

const appUrl = targetNetwork.appUrl
? getUrlWithSubstitutions(targetNetwork.appUrl, user, prefix)
: undefined;
Expand Down
Loading

0 comments on commit 8d8be8f

Please sign in to comment.