fix groups more
This commit is contained in:
parent
e5177a8f07
commit
c512a76e14
Binary file not shown.
Binary file not shown.
@ -452,7 +452,6 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
const channelUsers = channel.recipients?.map((recipient) => recipient.user.toPublicUser());
|
const channelUsers = channel.recipients?.map((recipient) => recipient.user.toPublicUser());
|
||||||
|
|
||||||
if (channelUsers && channelUsers.length > 0) channelUsers.forEach((user) => users.add(user));
|
if (channelUsers && channelUsers.length > 0) channelUsers.forEach((user) => users.add(user));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
flags: channel.flags,
|
flags: channel.flags,
|
||||||
@ -462,6 +461,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
icon: channel.icon,
|
icon: channel.icon,
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
is_spam: false, // TODO
|
is_spam: false, // TODO
|
||||||
|
owner_id: channel.owner_id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const generateDmChannelsTime = taskSw.getElapsedAndReset();
|
const generateDmChannelsTime = taskSw.getElapsedAndReset();
|
||||||
|
|||||||
@ -17,14 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import {
|
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
|
||||||
Column,
|
|
||||||
Entity,
|
|
||||||
JoinColumn,
|
|
||||||
ManyToOne,
|
|
||||||
OneToMany,
|
|
||||||
RelationId,
|
|
||||||
} from "typeorm";
|
|
||||||
import { DmChannelDTO } from "../dtos";
|
import { DmChannelDTO } from "../dtos";
|
||||||
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
|
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
|
||||||
import { InvisibleCharacters, Snowflake, emitEvent, getPermission, trimSpecial, Permissions, BitField } from "../util";
|
import { InvisibleCharacters, Snowflake, emitEvent, getPermission, trimSpecial, Permissions, BitField } from "../util";
|
||||||
@ -135,14 +128,10 @@ export class Channel extends BaseClass {
|
|||||||
})
|
})
|
||||||
messages?: Message[];
|
messages?: Message[];
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, {
|
||||||
() => VoiceState,
|
cascade: true,
|
||||||
(voice_state: VoiceState) => voice_state.channel,
|
orphanedRowAction: "delete",
|
||||||
{
|
})
|
||||||
cascade: true,
|
|
||||||
orphanedRowAction: "delete",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
voice_states?: VoiceState[];
|
voice_states?: VoiceState[];
|
||||||
|
|
||||||
@OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, {
|
@OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, {
|
||||||
@ -194,48 +183,22 @@ export class Channel extends BaseClass {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!opts?.skipNameChecks) {
|
if (!opts?.skipNameChecks) {
|
||||||
if (
|
if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) {
|
||||||
!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") &&
|
for (const character of InvisibleCharacters) if (channel.name.includes(character)) throw new HTTPError("Channel name cannot include invalid characters", 403);
|
||||||
channel.name
|
|
||||||
) {
|
|
||||||
for (const character of InvisibleCharacters)
|
|
||||||
if (channel.name.includes(character))
|
|
||||||
throw new HTTPError(
|
|
||||||
"Channel name cannot include invalid characters",
|
|
||||||
403,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Categories skip these checks on discord.com
|
// Categories skip these checks on discord.com
|
||||||
if (
|
if (channel.type !== ChannelType.GUILD_CATEGORY || guild.features.includes("IRC_LIKE_CATEGORY_NAMES")) {
|
||||||
channel.type !== ChannelType.GUILD_CATEGORY ||
|
if (channel.name.includes(" ")) throw new HTTPError("Channel name cannot include invalid characters", 403);
|
||||||
guild.features.includes("IRC_LIKE_CATEGORY_NAMES")
|
|
||||||
) {
|
|
||||||
if (channel.name.includes(" "))
|
|
||||||
throw new HTTPError(
|
|
||||||
"Channel name cannot include invalid characters",
|
|
||||||
403,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (channel.name.match(/--+/g))
|
if (channel.name.match(/--+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403);
|
||||||
throw new HTTPError(
|
|
||||||
"Channel name cannot include multiple adjacent dashes.",
|
|
||||||
403,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (channel.name.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-")
|
||||||
channel.name.charAt(0) === "-" ||
|
throw new HTTPError("Channel name cannot start/end with dash.", 403);
|
||||||
channel.name.charAt(channel.name.length - 1) === "-"
|
|
||||||
)
|
|
||||||
throw new HTTPError(
|
|
||||||
"Channel name cannot start/end with dash.",
|
|
||||||
403,
|
|
||||||
);
|
|
||||||
} else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com
|
} else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) {
|
if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) {
|
||||||
if (!channel.name)
|
if (!channel.name) throw new HTTPError("Channel name cannot be empty.", 403);
|
||||||
throw new HTTPError("Channel name cannot be empty.", 403);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,15 +210,8 @@ export class Channel extends BaseClass {
|
|||||||
const exists = await Channel.findOneOrFail({
|
const exists = await Channel.findOneOrFail({
|
||||||
where: { id: channel.parent_id },
|
where: { id: channel.parent_id },
|
||||||
});
|
});
|
||||||
if (!exists)
|
if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400);
|
||||||
throw new HTTPError(
|
if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild");
|
||||||
"Parent id channel doesn't exist",
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
if (exists.guild_id !== channel.guild_id)
|
|
||||||
throw new HTTPError(
|
|
||||||
"The category channel needs to be in the guild",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ChannelType.GUILD_CATEGORY:
|
case ChannelType.GUILD_CATEGORY:
|
||||||
@ -272,9 +228,7 @@ export class Channel extends BaseClass {
|
|||||||
if (!channel.permission_overwrites) channel.permission_overwrites = [];
|
if (!channel.permission_overwrites) channel.permission_overwrites = [];
|
||||||
// TODO: eagerly auto generate position of all guild channels
|
// TODO: eagerly auto generate position of all guild channels
|
||||||
|
|
||||||
const position =
|
const position = (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0;
|
||||||
(channel.type === ChannelType.UNHANDLED ? 0 : channel.position) ||
|
|
||||||
0;
|
|
||||||
|
|
||||||
channel = {
|
channel = {
|
||||||
...channel,
|
...channel,
|
||||||
@ -300,11 +254,7 @@ export class Channel extends BaseClass {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async createDMChannel(
|
static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
|
||||||
recipients: string[],
|
|
||||||
creator_user_id: string,
|
|
||||||
name?: string,
|
|
||||||
) {
|
|
||||||
recipients = recipients.distinct().filter((x) => x !== creator_user_id);
|
recipients = recipients.distinct().filter((x) => x !== creator_user_id);
|
||||||
// TODO: check config for max number of recipients
|
// TODO: check config for max number of recipients
|
||||||
/** if you want to disallow note to self channels, uncomment the conditional below
|
/** if you want to disallow note to self channels, uncomment the conditional below
|
||||||
@ -315,8 +265,7 @@ export class Channel extends BaseClass {
|
|||||||
}
|
}
|
||||||
**/
|
**/
|
||||||
|
|
||||||
const type =
|
const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM;
|
||||||
recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM;
|
|
||||||
|
|
||||||
let channel = null;
|
let channel = null;
|
||||||
|
|
||||||
@ -346,16 +295,13 @@ export class Channel extends BaseClass {
|
|||||||
channel = await Channel.create({
|
channel = await Channel.create({
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
owner_id: undefined,
|
owner_id: creator_user_id,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
last_message_id: undefined,
|
last_message_id: undefined,
|
||||||
recipients: channelRecipients.map((x) =>
|
recipients: channelRecipients.map((x) =>
|
||||||
Recipient.create({
|
Recipient.create({
|
||||||
user_id: x,
|
user_id: x,
|
||||||
closed: !(
|
closed: !(type === ChannelType.GROUP_DM || x === creator_user_id),
|
||||||
type === ChannelType.GROUP_DM ||
|
|
||||||
x === creator_user_id
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
nsfw: false,
|
nsfw: false,
|
||||||
@ -386,9 +332,7 @@ export class Channel extends BaseClass {
|
|||||||
|
|
||||||
static async removeRecipientFromChannel(channel: Channel, user_id: string) {
|
static async removeRecipientFromChannel(channel: Channel, user_id: string) {
|
||||||
await Recipient.delete({ channel_id: channel.id, user_id: user_id });
|
await Recipient.delete({ channel_id: channel.id, user_id: user_id });
|
||||||
channel.recipients = channel.recipients?.filter(
|
channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id);
|
||||||
(r) => r.user_id !== user_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (channel.recipients?.length === 0) {
|
if (channel.recipients?.length === 0) {
|
||||||
await Channel.deleteChannel(channel);
|
await Channel.deleteChannel(channel);
|
||||||
@ -440,20 +384,11 @@ export class Channel extends BaseClass {
|
|||||||
select: { channel_ordering: true },
|
select: { channel_ordering: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedOrdering = guild.channel_ordering.filter(
|
const updatedOrdering = guild.channel_ordering.filter((id) => id != channel.id);
|
||||||
(id) => id != channel.id,
|
await Guild.update({ id: channel.guild_id }, { channel_ordering: updatedOrdering });
|
||||||
);
|
|
||||||
await Guild.update(
|
|
||||||
{ id: channel.guild_id },
|
|
||||||
{ channel_ordering: updatedOrdering },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async calculatePosition(
|
static async calculatePosition(channel_id: string, guild_id: string, guild?: Guild) {
|
||||||
channel_id: string,
|
|
||||||
guild_id: string,
|
|
||||||
guild?: Guild,
|
|
||||||
) {
|
|
||||||
if (!guild)
|
if (!guild)
|
||||||
guild = await Guild.findOneOrFail({
|
guild = await Guild.findOneOrFail({
|
||||||
where: { id: guild_id },
|
where: { id: guild_id },
|
||||||
@ -470,11 +405,7 @@ export class Channel extends BaseClass {
|
|||||||
select: { channel_ordering: true },
|
select: { channel_ordering: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const channels = await Promise.all(
|
const channels = await Promise.all(guild.channel_ordering.map((id) => Channel.findOne({ where: { id } })));
|
||||||
guild.channel_ordering.map((id) =>
|
|
||||||
Channel.findOne({ where: { id } }),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
.filter((channel) => channel !== null)
|
.filter((channel) => channel !== null)
|
||||||
@ -488,22 +419,16 @@ export class Channel extends BaseClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDm() {
|
isDm() {
|
||||||
return (
|
return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM;
|
||||||
this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does the channel support sending messages ( eg categories do not )
|
// Does the channel support sending messages ( eg categories do not )
|
||||||
isWritable() {
|
isWritable() {
|
||||||
const disallowedChannelTypes = [
|
const disallowedChannelTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_STAGE_VOICE, ChannelType.VOICELESS_WHITEBOARD];
|
||||||
ChannelType.GUILD_CATEGORY,
|
|
||||||
ChannelType.GUILD_STAGE_VOICE,
|
|
||||||
ChannelType.VOICELESS_WHITEBOARD,
|
|
||||||
];
|
|
||||||
return disallowedChannelTypes.indexOf(this.type) == -1;
|
return disallowedChannelTypes.indexOf(this.type) == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserPermissions(opts: {user_id?: string, user?: User, member?: Member, guild?: Guild}): Promise<Permissions> {
|
async getUserPermissions(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise<Permissions> {
|
||||||
let guild = opts.guild;
|
let guild = opts.guild;
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
if (this.guild) guild = this.guild;
|
if (this.guild) guild = this.guild;
|
||||||
@ -523,20 +448,21 @@ export class Channel extends BaseClass {
|
|||||||
|
|
||||||
let member = opts.member;
|
let member = opts.member;
|
||||||
if (!member) {
|
if (!member) {
|
||||||
if (opts.user) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user.id }, relations: [ "roles" ] });
|
if (opts.user) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user.id }, relations: ["roles"] });
|
||||||
else if (opts.user_id) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user_id }, relations: [ "roles" ] });
|
else if (opts.user_id) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user_id }, relations: ["roles"] });
|
||||||
else {
|
else {
|
||||||
console.error("Channel.getUserPermissions: called without user or member for non-DM channel.");
|
console.error("Channel.getUserPermissions: called without user or member for non-DM channel.");
|
||||||
return Permissions.NONE;
|
return Permissions.NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const roles = (member.roles || (await Member.findOneOrFail({ where: { guild_id: guild.id, index: member.index }, relations: [ "roles" ] })).roles)
|
const roles = (member.roles || (await Member.findOneOrFail({ where: { guild_id: guild.id, index: member.index }, relations: ["roles"] })).roles).sort(
|
||||||
.sort((a, b) => a.position - b.position); // ascending by position
|
(a, b) => a.position - b.position,
|
||||||
|
); // ascending by position
|
||||||
|
|
||||||
// calculate user's channel perms - should in theory match https://docs.discord.food/topics/permissions#permission-overwrites
|
// calculate user's channel perms - should in theory match https://docs.discord.food/topics/permissions#permission-overwrites
|
||||||
// start at role permissions
|
// start at role permissions
|
||||||
let userPerms = new Permissions(new BitField(0).add(roles.map(r => r.permissions)));
|
let userPerms = new Permissions(new BitField(0).add(roles.map((r) => r.permissions)));
|
||||||
|
|
||||||
// TODO: do we want to have an instance-wide opt out of this behavior? It would just be an extra if statement here
|
// TODO: do we want to have an instance-wide opt out of this behavior? It would just be an extra if statement here
|
||||||
if (userPerms.has(Permissions.FLAGS.ADMINISTRATOR)) return userPerms;
|
if (userPerms.has(Permissions.FLAGS.ADMINISTRATOR)) return userPerms;
|
||||||
@ -544,11 +470,11 @@ export class Channel extends BaseClass {
|
|||||||
// apply channel overrides
|
// apply channel overrides
|
||||||
if (this.permission_overwrites) {
|
if (this.permission_overwrites) {
|
||||||
// role overwrites - TODO: this probably violates the geneva conventions - we should probably be ordering roles here
|
// role overwrites - TODO: this probably violates the geneva conventions - we should probably be ordering roles here
|
||||||
for (const overwrite of this.permission_overwrites.filter(o => o.type === ChannelPermissionOverwriteType.role && roles.map(r => r.id).includes(o.id)))
|
for (const overwrite of this.permission_overwrites.filter((o) => o.type === ChannelPermissionOverwriteType.role && roles.map((r) => r.id).includes(o.id)))
|
||||||
userPerms = new Permissions(userPerms.remove(overwrite.deny).add(overwrite.allow));
|
userPerms = new Permissions(userPerms.remove(overwrite.deny).add(overwrite.allow));
|
||||||
|
|
||||||
// member overwrite, throws if somehow we have multiple overwrites for the same member
|
// member overwrite, throws if somehow we have multiple overwrites for the same member
|
||||||
const memberOverwrite = this.permission_overwrites.single(o => o.type === ChannelPermissionOverwriteType.member && o.id === member?.id);
|
const memberOverwrite = this.permission_overwrites.single((o) => o.type === ChannelPermissionOverwriteType.member && o.id === member?.id);
|
||||||
if (memberOverwrite) userPerms = new Permissions(userPerms.remove(memberOverwrite.deny).add(memberOverwrite.allow));
|
if (memberOverwrite) userPerms = new Permissions(userPerms.remove(memberOverwrite.deny).add(memberOverwrite.allow));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,8 +482,8 @@ export class Channel extends BaseClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should we throw for missing args?
|
// TODO: should we throw for missing args?
|
||||||
async canViewChannel(opts: {user_id?: string, user?: User, member?: Member, guild?: Guild}): Promise<boolean> {
|
async canViewChannel(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise<boolean> {
|
||||||
if(this.isDm()) return await this.canViewDmChannel(opts.user_id, opts.user);
|
if (this.isDm()) return await this.canViewDmChannel(opts.user_id, opts.user);
|
||||||
|
|
||||||
const userPerms = await this.getUserPermissions(opts);
|
const userPerms = await this.getUserPermissions(opts);
|
||||||
return userPerms.has("VIEW_CHANNEL");
|
return userPerms.has("VIEW_CHANNEL");
|
||||||
@ -570,9 +496,9 @@ export class Channel extends BaseClass {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
if (this.recipients)
|
if (this.recipients) return this.recipients.some((r) => r.user_id === user.id && !r.closed);
|
||||||
return this.recipients.some((r) => r.user_id === user.id && !r.closed);
|
else {
|
||||||
else { // we dont have recipients on hand
|
// we dont have recipients on hand
|
||||||
const recipient = await Recipient.findOne({ where: { channel_id: this.id, user_id: user.id } });
|
const recipient = await Recipient.findOne({ where: { channel_id: this.id, user_id: user.id } });
|
||||||
return recipient == null ? false : !recipient.closed;
|
return recipient == null ? false : !recipient.closed;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user