From c512a76e143569e0452b02b2b231ffd8ea7a0ef9 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 20 Nov 2025 14:34:23 -0600 Subject: [PATCH] fix groups more --- assets/openapi.json | Bin 1384857 -> 1384128 bytes assets/schemas.json | Bin 4239592 -> 4238410 bytes src/gateway/opcodes/Identify.ts | 2 +- src/util/entities/Channel.ts | 156 +++++++++----------------------- 4 files changed, 42 insertions(+), 116 deletions(-) diff --git a/assets/openapi.json b/assets/openapi.json index 5f6154660e9ac5f57ba724f3d576c059fa299dba..1805c9a326ec82d84970ca9c6db26193f287c436 100644 GIT binary patch delta 1037 zcmb7DOGs2<7-jU%{NHu1k4Y)eoQI8$+BkE^oQH20QW9ojQ&@_Pq)_H#(jJQz4k;QH zWDa<;G+nm{iQsMwgf>y3QbY?OhTKK@K!_lu{yU>2ZM4PVf82A-ExVo7JvX{Lds^H2oPF)R*V}vD(G{))W*Hvx3LIKx$g3-z z-;-tn5t|N>D+EHw%+|xxg93W;Sdwy(*iX%_9txjZMZ40}cw3{U%WA5FD0F zs460cM^3Us{+4LMnqM#>MJ9*JUzykBjlGRCz^n+>FuSM#fhp059v3+g`6QUpUrGsB zbdfb26hs6cia9ZdxFdB9?XjJyob%3m$*NjZHj-_}*rV}!bo2O!jDq2;Jc64eWpP?5T#9aQbu&c^Ah}@`M3oSujySG z9l}N#Z@^g2jgFV|VlBlk2QrBBP> z|17Iiqzls1%s5{$9(c7L%-xhuqw$uMyv>e%62U8yCmbrbsC^cre#wv#t7D59UjJf2 zL7T*2y(bw)8~DG|EEI&RI0nWhk;`sN2;4kP zsMd~GyBFw76(nTJSCj9$1Vk|J!KUqFum^{BToU)@6sP7I$h(KTv)QHer8nf`04hB~ zJb#gkE~Ggp#s@hOw^C-e5&cvFP-+7=#s&l%ZfuekT>n9Nxi(M=IlsaCAo23T>KP|) z^*aE=GWaq5q|AcL=eZMjIsyVdkpFJdnE)HwFTf$zB!7+sQ1NgsTq_jZYk^A4y|7lH z(&uQzPYN^_de#^Ng8`5YO^I%*iIuGcpsO@@GzhJw+PybT#zp^3^oC%E{%i<5J7&eA zz~k%CKlHal+kcnvtsQ(jmLM%!d^q>s-el-!g4d7GJ+Z~sXSw1f=r9g@!{9Ad{mW(o z9O;L;l4W(O5GvvF&YsbsL#gg$2|&Pa(+LO$6h^P;F?Zwz-Um8pQ~>oY?+Yr4@@iHHfkcr$-a?oh`QUf<5e$k3@d?ET8-kyX527xZ9nGsHfsVz6A>0`VV1s6?m zsA$5~DX9Y}cnX1{OqV*uS+_~W>#p)ToO^C_VEt{r3lkH>gKM)w5MyS->!S&Aq}HrO zuxryA#<`@}kJ+qPzFkNE3klPo1!kX=zi)M-WOHb^{D`Z;T!iy7_C8fnrslIEg^j{a TQBEOII4CM8_UrT6O8L%jplpHb diff --git a/assets/schemas.json b/assets/schemas.json index 0b0307db573ab0d98b3df3dc8983f39ea18bf1d5..a60d85e4fe73f30f807c8eb15b322ca6caf2b656 100644 GIT binary patch delta 2081 zcmd7RTWnKx90&09etOP--CEXNShx0+uCSr)*w$?ohV_!TY#Jv%O@wWU#2dQ=1ZA4g zZLP(wBw(>$O}sQlvt$}m#dsv~vJsUi4_h=|0x^~+(FcPG#%Lt^ZL2|jS$%-`@M+I! zfB)bA_dS>M=VtVud^Mww#&j`#%n&oiOfhrJ@}{)V=NTJ)=)SSh@wQO#=81>LcTbGo zvp2YR*Pi{m_JoSxP+dw$`>jvv*Oyld)^;bB2PCp;%LDQ|<7F*WvG|j`6Ac&T6b2{k zQl?+3#S62QR^%?qpO)7`V+)&c)mn|2ves403el&u_rH^OOIZ2ZVnpsviy6;_6iGY% znS4}NR)fp2oarNaBUW- z>A~tPI4~ock-g-qFV&u$5lyK2MXt-t3V%9kvtm-N2w`$o^jtoR&arFwOECmRGkc3| zl$4M$zrj|WUO8p`+un}2a2NZbEhQuj#f9t70pi^EUxN-mF1)2d4<59-k$=Ov6}|+A zFCIOa;EKZVnL~f27&$A9$RD>@Fm;IgL_QA=!1I>D%Zt(oHOX$bB!!CK6GDMS~$>Nn^wmg^*v$O9ajGTh{K5s z+tu4JIHXn<7ysjTKeBhJUgQoKx>2=I<3jd+rL|bA?+W8wwNceBWYt%77}=(-(L#@^ zFPX0T1PM++URp;!Y9>Ferw!CXt+bJ@p=&8XL29E-)J`4LNg?W@Zt9^hT}PW~3-!`g XCcplsfvX<3aKUpr?bnLP4=?@!5zGsb delta 2463 zcmdUvduSVV9Kg9WP44kql67sB5*M5~ZS=Xiiic&5i1!`giF!gSM@^2ln_L>lyM5_IK^-exz%_H`qP! zO!t7duWN7!rcSWD^mCAYShEU4K*P;&sZo~?a|@b#MWKU67)j`0yPmqWzl4c$SCL~B zPBDfDA@W_pM^gAY`{F8Gc)6^6FwL@c(t(p~J#*_yl$JAGwwz{5+3YR{A<$ls3;m+G z8s2`7-jnF%bkdnQrj>$F!s>vDZ&@0)9O1SmhMkG&UM@^Q>USmvbF(=%Xx>a4v#0o1 zdLO32(8;mz!opH0*?Fx1nJATpLYEfzP;OJL4g%IUcx6OKk&c#^{cR z$2~A{)ntxOXlOWl!jPvtB0X9+1YTcPtC;%Xxv16}Kl>>IfqA9@BF_3sSUi&Fg-M51 zPdxx9n84;8leWJ?rxfaok3z!zSbVs2~tlxS*`)c z8FN4m>X-j~oFD}0Tu2?J%i;TRLc{ibb}KlFyzQ`Ygp|u#uWAxmsI7v~Qn4A52M7aS zjSwtX{war2Y#rERgazLyN&ALlguwFD%I7bm#G^(rjr#1cK0-=mUyCZ6m3^OntIP`F zi_KLTit{lH@aw$xZ;FFo5|>&eC+T?Ql^7#s87b}=BaVzn?}*siwx7z=B{>a>a^ZBj3$Q|i;j!qu6I z64msDVUBPEChu_89aj+9Ety^d?Aa~cEzE~Mycn6!{h5boey zl5eT)4V$K^5%>{rrE+pujtNXV_zG~m%o`P-KkF86Xzc1Sq&{!CA42*-lVUZM^zab6 z+@_NjhWLl{w=>%-lz8(Hk2nz*;zm438B&f^AeBfJatCrJ;zg>h0%|mU_etUbBb})f!NB;l;RHh67 diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 46c7db6c..7b9f107b 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -452,7 +452,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { const channelUsers = channel.recipients?.map((recipient) => recipient.user.toPublicUser()); if (channelUsers && channelUsers.length > 0) channelUsers.forEach((user) => users.add(user)); - return { id: channel.id, flags: channel.flags, @@ -462,6 +461,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { icon: channel.icon, name: channel.name, is_spam: false, // TODO + owner_id: channel.owner_id, }; }); const generateDmChannelsTime = taskSw.getElapsedAndReset(); diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts index 3c11de37..a5995726 100644 --- a/src/util/entities/Channel.ts +++ b/src/util/entities/Channel.ts @@ -17,14 +17,7 @@ */ import { HTTPError } from "lambert-server"; -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - RelationId, -} from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { DmChannelDTO } from "../dtos"; import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; import { InvisibleCharacters, Snowflake, emitEvent, getPermission, trimSpecial, Permissions, BitField } from "../util"; @@ -135,14 +128,10 @@ export class Channel extends BaseClass { }) messages?: Message[]; - @OneToMany( - () => VoiceState, - (voice_state: VoiceState) => voice_state.channel, - { - cascade: true, - orphanedRowAction: "delete", - }, - ) + @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { + cascade: true, + orphanedRowAction: "delete", + }) voice_states?: VoiceState[]; @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { @@ -194,48 +183,22 @@ export class Channel extends BaseClass { }); if (!opts?.skipNameChecks) { - if ( - !guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && - channel.name - ) { - for (const character of InvisibleCharacters) - if (channel.name.includes(character)) - throw new HTTPError( - "Channel name cannot include invalid characters", - 403, - ); + if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && 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 - if ( - channel.type !== ChannelType.GUILD_CATEGORY || - guild.features.includes("IRC_LIKE_CATEGORY_NAMES") - ) { - if (channel.name.includes(" ")) - throw new HTTPError( - "Channel name cannot include invalid characters", - 403, - ); + if (channel.type !== ChannelType.GUILD_CATEGORY || 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)) - throw new HTTPError( - "Channel name cannot include multiple adjacent dashes.", - 403, - ); + if (channel.name.match(/--+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403); - if ( - channel.name.charAt(0) === "-" || - channel.name.charAt(channel.name.length - 1) === "-" - ) - throw new HTTPError( - "Channel name cannot start/end with dash.", - 403, - ); + if (channel.name.charAt(0) === "-" || 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 } if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) { - if (!channel.name) - throw new HTTPError("Channel name cannot be empty.", 403); + if (!channel.name) throw new HTTPError("Channel name cannot be empty.", 403); } } @@ -247,15 +210,8 @@ export class Channel extends BaseClass { const exists = await Channel.findOneOrFail({ where: { id: channel.parent_id }, }); - if (!exists) - throw new HTTPError( - "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", - ); + if (!exists) throw new HTTPError("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; case ChannelType.GUILD_CATEGORY: @@ -272,9 +228,7 @@ export class Channel extends BaseClass { if (!channel.permission_overwrites) channel.permission_overwrites = []; // TODO: eagerly auto generate position of all guild channels - const position = - (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || - 0; + const position = (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0; channel = { ...channel, @@ -300,11 +254,7 @@ export class Channel extends BaseClass { return ret; } - static async createDMChannel( - recipients: string[], - creator_user_id: string, - name?: string, - ) { + static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { recipients = recipients.distinct().filter((x) => x !== creator_user_id); // TODO: check config for max number of recipients /** if you want to disallow note to self channels, uncomment the conditional below @@ -315,8 +265,7 @@ export class Channel extends BaseClass { } **/ - const type = - recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM; + const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM; let channel = null; @@ -346,16 +295,13 @@ export class Channel extends BaseClass { channel = await Channel.create({ name, type, - owner_id: undefined, + owner_id: creator_user_id, created_at: new Date(), last_message_id: undefined, recipients: channelRecipients.map((x) => Recipient.create({ user_id: x, - closed: !( - type === ChannelType.GROUP_DM || - x === creator_user_id - ), + closed: !(type === ChannelType.GROUP_DM || x === creator_user_id), }), ), nsfw: false, @@ -386,9 +332,7 @@ export class Channel extends BaseClass { static async removeRecipientFromChannel(channel: Channel, user_id: string) { await Recipient.delete({ channel_id: channel.id, user_id: user_id }); - channel.recipients = channel.recipients?.filter( - (r) => r.user_id !== user_id, - ); + channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id); if (channel.recipients?.length === 0) { await Channel.deleteChannel(channel); @@ -440,20 +384,11 @@ export class Channel extends BaseClass { select: { channel_ordering: true }, }); - const updatedOrdering = guild.channel_ordering.filter( - (id) => id != channel.id, - ); - await Guild.update( - { id: channel.guild_id }, - { channel_ordering: updatedOrdering }, - ); + const updatedOrdering = guild.channel_ordering.filter((id) => id != channel.id); + await Guild.update({ id: channel.guild_id }, { channel_ordering: updatedOrdering }); } - static async calculatePosition( - channel_id: string, - guild_id: string, - guild?: Guild, - ) { + static async calculatePosition(channel_id: string, guild_id: string, guild?: Guild) { if (!guild) guild = await Guild.findOneOrFail({ where: { id: guild_id }, @@ -470,11 +405,7 @@ export class Channel extends BaseClass { select: { channel_ordering: true }, }); - const channels = await Promise.all( - guild.channel_ordering.map((id) => - Channel.findOne({ where: { id } }), - ), - ); + const channels = await Promise.all(guild.channel_ordering.map((id) => Channel.findOne({ where: { id } }))); return channels .filter((channel) => channel !== null) @@ -488,22 +419,16 @@ export class Channel extends BaseClass { } isDm() { - return ( - this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM - ); + return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM; } // Does the channel support sending messages ( eg categories do not ) isWritable() { - const disallowedChannelTypes = [ - ChannelType.GUILD_CATEGORY, - ChannelType.GUILD_STAGE_VOICE, - ChannelType.VOICELESS_WHITEBOARD, - ]; + const disallowedChannelTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_STAGE_VOICE, ChannelType.VOICELESS_WHITEBOARD]; return disallowedChannelTypes.indexOf(this.type) == -1; } - async getUserPermissions(opts: {user_id?: string, user?: User, member?: Member, guild?: Guild}): Promise { + async getUserPermissions(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise { let guild = opts.guild; if (!guild) { if (this.guild) guild = this.guild; @@ -523,20 +448,21 @@ export class Channel extends BaseClass { let member = opts.member; if (!member) { - 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" ] }); + 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 { console.error("Channel.getUserPermissions: called without user or member for non-DM channel."); return Permissions.NONE; } } - const roles = (member.roles || (await Member.findOneOrFail({ where: { guild_id: guild.id, index: member.index }, relations: [ "roles" ] })).roles) - .sort((a, b) => a.position - b.position); // ascending by position + const roles = (member.roles || (await Member.findOneOrFail({ where: { guild_id: guild.id, index: member.index }, relations: ["roles"] })).roles).sort( + (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 // 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 if (userPerms.has(Permissions.FLAGS.ADMINISTRATOR)) return userPerms; @@ -544,11 +470,11 @@ export class Channel extends BaseClass { // apply channel overrides if (this.permission_overwrites) { // 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)); // 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)); } @@ -556,8 +482,8 @@ export class Channel extends BaseClass { } // TODO: should we throw for missing args? - async canViewChannel(opts: {user_id?: string, user?: User, member?: Member, guild?: Guild}): Promise { - if(this.isDm()) return await this.canViewDmChannel(opts.user_id, opts.user); + async canViewChannel(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise { + if (this.isDm()) return await this.canViewDmChannel(opts.user_id, opts.user); const userPerms = await this.getUserPermissions(opts); return userPerms.has("VIEW_CHANNEL"); @@ -570,9 +496,9 @@ export class Channel extends BaseClass { return false; } if (!user) return false; - if (this.recipients) - return this.recipients.some((r) => r.user_id === user.id && !r.closed); - else { // we dont have recipients on hand + if (this.recipients) return this.recipients.some((r) => r.user_id === user.id && !r.closed); + else { + // we dont have recipients on hand const recipient = await Recipient.findOne({ where: { channel_id: this.id, user_id: user.id } }); return recipient == null ? false : !recipient.closed; }