Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: teams support #3350

Merged
merged 10 commits into from Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.js
Expand Up @@ -85,6 +85,8 @@ module.exports = {
ReactionEmoji: require('./structures/ReactionEmoji'),
RichPresenceAssets: require('./structures/Presence').RichPresenceAssets,
Role: require('./structures/Role'),
Team: require('./structures/Team'),
TeamMember: require('./structures/TeamMember'),
TextChannel: require('./structures/TextChannel'),
User: require('./structures/User'),
VoiceChannel: require('./structures/VoiceChannel'),
Expand Down
5 changes: 3 additions & 2 deletions src/structures/ClientApplication.js
Expand Up @@ -3,6 +3,7 @@
const Snowflake = require('../util/Snowflake');
const { ClientApplicationAssetTypes, Endpoints } = require('../util/Constants');
const Base = require('./Base');
const Team = require('./Team');

const AssetTypes = Object.keys(ClientApplicationAssetTypes);

Expand Down Expand Up @@ -67,9 +68,9 @@ class ClientApplication extends Base {

/**
* The owner of this OAuth application
* @type {?User}
* @type {User|Team}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Owner and Team should be separated in my opinion. While the owner of an app that is in a team is fake, it's still an user. A warning should be added instead

*/
this.owner = data.owner ? this.client.users.add(data.owner) : null;
this.owner = data.team ? new Team(this.client, data.team) : this.client.users.add(data.owner);
}

/**
Expand Down
109 changes: 109 additions & 0 deletions src/structures/Team.js
@@ -0,0 +1,109 @@
'use strict';

const Snowflake = require('../util/Snowflake');
const Collection = require('../util/Collection');
const Base = require('./Base');
const TeamMember = require('./TeamMember');

/**
* Represents a Client OAuth2 Application Team.
* @extends {Base}
*/
class Team extends Base {
constructor(client, data) {
super(client);
this._patch(data);
}

_patch(data) {
/**
* The ID of the Team
* @type {Snowflake}
*/
this.id = data.id;

/**
* The name of the Team
* @type {string}
*/
this.name = data.name;

/**
* The Team's icon hash
* @type {?string}
*/
this.icon = data.icon || null;

/**
* The Team's owner id
* @type {?string}
*/
this.ownerID = data.owner_user_id || null;

/**
* The Team's members
* @type {Collection<Snowflake, TeamMember>}
*/
this.members = new Collection();

for (const memberData of data.members) {
const member = new TeamMember(this.client, this, memberData);
this.members.set(member.id, member);
}
}

/**
* The owner of this team
* @type {?TeamMember}
* @readonly
*/
get owner() {
return this.members.get(this.ownerID) || null;
}

/**
* The timestamp the app was created at
* @type {number}
* @readonly
*/
get createdTimestamp() {
return Snowflake.deconstruct(this.id).timestamp;
}

/**
* The time the app was created at
* @type {Date}
* @readonly
*/
get createdAt() {
return new Date(this.createdTimestamp);
}

/**
* A link to the application's icon.
* @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {?string} URL to the icon
*/
iconURL({ format, size } = {}) {
if (!this.icon) return null;
return this.client.rest.cdn.TeamIcon(this.id, this.icon, { format, size });
}

/**
* When concatenated with a string, this automatically returns the Team's name instead of the
* Team object.
* @returns {string}
* @example
* // Logs: Team name: My Team
* console.log(`Team name: ${team}`);
*/
toString() {
return this.name;
}

toJSON() {
return super.toJSON({ createdTimestamp: true });
}
}

module.exports = Team;
62 changes: 62 additions & 0 deletions src/structures/TeamMember.js
@@ -0,0 +1,62 @@
'use strict';

const Base = require('./Base');
const { MembershipStates } = require('../util/Constants');

/**
* Represents a Client OAuth2 Application Team Member.
* @extends {Base}
*/
class TeamMember extends Base {
constructor(client, team, data) {
super(client);

/**
* The Team this member is part of
* @type {Team}
*/
this.team = team;

this._patch(data);
}

_patch(data) {
/**
* The permissions this Team Member has with reguard to the team
* @type {string[]}
*/
this.permissions = data.permissions;

/**
* The permissions this Team Member has with reguard to the team
* @type {MembershipStates}
*/
this.membershipState = MembershipStates[data.membership_state];

/**
* The user for this Team Member
* @type {User}
*/
this.user = this.client.users.add(data.user);

/**
* The ID of the Team Member
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant it. This is redundant...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamMembers are keyed by id in Team#members, the only id we have to relate to is the user id, which is no different than how GuildMembers' ids work.

I thought your original complaint is we weren't using the User instead of the raw user data, so that's why I made the change I did, which really doesn't matter which way.

* @type {Snowflake}
*/
this.id = this.user.id;
}

/**
* When concatenated with a string, this automatically returns the team members's tag instead of the
* TeamMember object.
* @returns {string}
* @example
* // Logs: Team Member's tag: @Hydrabolt
* console.log(`Team Member's tag: ${teamMember}`);
*/
toString() {
return this.user.toString();
}
}

module.exports = TeamMember;
15 changes: 15 additions & 0 deletions src/util/Constants.js
Expand Up @@ -145,6 +145,8 @@ exports.Endpoints = {
makeImageUrl(`${root}/channel-icons/${channelID}/${hash}`, { size, format }),
Splash: (guildID, hash, format = 'webp', size) =>
makeImageUrl(`${root}/splashes/${guildID}/${hash}`, { size, format }),
TeamIcon: (teamID, hash, { format = 'webp', size } = {}) =>
makeImageUrl(`${root}/team-icons/${teamID}/${hash}`, { size, format }),
};
},
invite: (root, code) => `${root}/${code}`,
Expand Down Expand Up @@ -569,6 +571,19 @@ exports.DefaultMessageNotifications = [
'MENTIONS',
];

/**
* The value set for a team members's membership state:
* * INVITED
* * ACCEPTED
* @typedef {string} MembershipStates
*/
exports.MembershipStates = [
bdistin marked this conversation as resolved.
Show resolved Hide resolved
// They start at 1
null,
'INVITED',
'ACCEPTED',
];

function keyMirror(arr) {
let tmp = Object.create(null);
for (const value of arr) tmp[value] = value;
Expand Down
31 changes: 31 additions & 0 deletions typings/index.d.ts
Expand Up @@ -264,6 +264,34 @@ declare module 'discord.js' {
public toString(): string;
}

export class Team extends Base {
constructor(client: Client, data: object);
public id: Snowflake;
public name: string;
public icon: string | null;
public ownerID: Snowflake | null;
public members: Collection<Snowflake, TeamMember>;

public readonly owner: TeamMember;
public readonly createdAt: Date;
public readonly createdTimestamp: number;

public iconURL(options?: AvatarOptions): string;
public toJSON(): object;
public toString(): string;
}

export class TeamMember extends Base {
constructor(client: Client, team: Team, data: object);
public team: Team;
public id: Snowflake;
public permissions: string[];
public membershipState: MembershipStates;
public user: User;

public toString(): string;
}

export interface ActivityOptions {
name?: string;
url?: string;
Expand Down Expand Up @@ -1992,6 +2020,9 @@ declare module 'discord.js' {

type InviteResolvable = string;

type MembershipStates = 'INVITED'
| 'ACCEPTED';

interface MessageCollectorOptions extends CollectorOptions {
max?: number;
maxProcessed?: number;
Expand Down