diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 1cc4a538..4bf81901 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -14,19 +14,23 @@ import { Webhook } from "./Webhook";
import { DmChannelDTO } from "../dtos";
export enum ChannelType {
- GUILD_TEXT = 0, // a text channel within a server
+ GUILD_TEXT = 0, // a text channel within a guild
DM = 1, // a direct message between users
- GUILD_VOICE = 2, // a voice channel within a server
+ GUILD_VOICE = 2, // a voice channel within a guild
GROUP_DM = 3, // a direct message between multiple users
- GUILD_CATEGORY = 4, // an organizational category that contains up to 50 channels
- GUILD_NEWS = 5, // a channel that users can follow and crosspost into their own server
- GUILD_STORE = 6, // a channel in which game developers can sell their game on Discord
+ GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels
+ GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route
+ GUILD_STORE = 6, // a channel in which game developers can sell their things
ENCRYPTED = 7, // end-to-end encrypted channel
ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel
+ TRANSACTIONAL = 9, // event chain style transactional channel
GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel
GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel
GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience
+ TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12
+ KANBAN = 34, // confluence like kanban board
+ VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage)
CUSTOM_START = 64, // start custom channel types from here
UNHANDLED = 255 // unhandled unowned pass-through channel type
}
@@ -72,7 +76,7 @@ export class Channel extends BaseClass {
@ManyToOne(() => Channel)
parent?: Channel;
- // only for group dms
+ // for group DMs and owned custom channel types
@Column({ nullable: true })
@RelationId((channel: Channel) => channel.owner)
owner_id: string;
@@ -117,6 +121,9 @@ export class Channel extends BaseClass {
})
invites?: Invite[];
+ @Column({ nullable: true })
+ retention_policy_id?: string;
+
@OneToMany(() => Message, (message: Message) => message.channel, {
cascade: true,
orphanedRowAction: "delete",
@@ -140,7 +147,7 @@ export class Channel extends BaseClass {
orphanedRowAction: "delete",
})
webhooks?: Webhook[];
-
+
// TODO: DM channel
static async createChannel(
channel: Partial<Channel>,
@@ -182,6 +189,7 @@ export class Channel extends BaseClass {
switch (channel.type) {
case ChannelType.GUILD_TEXT:
+ case ChannelType.GUILD_NEWS:
case ChannelType.GUILD_VOICE:
if (channel.parent_id && !opts?.skipExistsCheck) {
const exists = await Channel.findOneOrFail({ id: channel.parent_id });
@@ -191,25 +199,24 @@ export class Channel extends BaseClass {
}
break;
case ChannelType.GUILD_CATEGORY:
+ case ChannelType.UNHANDLED:
break;
case ChannelType.DM:
case ChannelType.GROUP_DM:
throw new HTTPError("You can't create a dm channel in a guild");
- // TODO: check if guild is community server
case ChannelType.GUILD_STORE:
- case ChannelType.GUILD_NEWS:
default:
throw new HTTPError("Not yet supported");
}
if (!channel.permission_overwrites) channel.permission_overwrites = [];
- // TODO: auto generate position
+ // TODO: eagerly auto generate position of all guild channels
channel = {
...channel,
...(!opts?.keepId && { id: Snowflake.generate() }),
created_at: new Date(),
- position: channel.position || 0,
+ position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0,
};
await Promise.all([
@@ -231,11 +238,13 @@ export class Channel extends BaseClass {
const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
// TODO: check config for max number of recipients
+ /** if you want to disallow note to self channels, uncomment the conditional below
if (otherRecipientsUsers.length !== recipients.length) {
throw new HTTPError("Recipient/s not found");
}
+ **/
- const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
+ const type = recipients.length > 1 ? ChannelType.DM : ChannelType.GROUP_DM;
let channel = null;
@@ -288,7 +297,8 @@ export class Channel extends BaseClass {
await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
}
- return channel_dto.excludedRecipients([creator_user_id]);
+ if (recipients.length === 1) return channel_dto;
+ else return channel_dto.excludedRecipients([creator_user_id]);
}
static async removeRecipientFromChannel(channel: Channel, user_id: string) {
@@ -354,4 +364,5 @@ export interface ChannelPermissionOverwrite {
export enum ChannelPermissionOverwriteType {
role = 0,
member = 1,
+ group = 2,
}
diff --git a/util/src/entities/ClientRelase.ts b/util/src/entities/ClientRelease.ts
index e021b82b..c5afd307 100644
--- a/util/src/entities/ClientRelase.ts
+++ b/util/src/entities/ClientRelease.ts
@@ -1,8 +1,8 @@
import { Column, Entity} from "typeorm";
import { BaseClass } from "./BaseClass";
-@Entity("client_relase")
-export class Relase extends BaseClass {
+@Entity("client_release")
+export class Release extends BaseClass {
@Column()
name: string;
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index f4a266dc..8d29b387 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -188,8 +188,8 @@ export interface ConfigValue {
},
client: {
useTestClient: Boolean;
- relases: {
- useLocalRelases: Boolean; //TODO
+ releases: {
+ useLocalRelease: Boolean; //TODO
upstreamVersion: string;
}
},
@@ -222,7 +222,7 @@ export const DefaultConfigOptions: ConfigValue = {
},
general: {
instanceName: "Fosscord Instance",
- instanceDescription: "This is a Fosscord instance made in pre-relase days",
+ instanceDescription: "This is a Fosscord instance made in pre-release days",
frontPage: null,
tosPage: null,
correspondenceEmail: "noreply@localhost.local",
@@ -389,8 +389,8 @@ export const DefaultConfigOptions: ConfigValue = {
},
client: {
useTestClient: true,
- relases: {
- useLocalRelases: true,
+ releases: {
+ useLocalRelease: true,
upstreamVersion: "0.0.264"
}
},
diff --git a/util/src/entities/ConnectedAccount.ts b/util/src/entities/ConnectedAccount.ts
index b8aa2889..09ae30ab 100644
--- a/util/src/entities/ConnectedAccount.ts
+++ b/util/src/entities/ConnectedAccount.ts
@@ -2,7 +2,7 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
-export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verifie"> {}
+export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verified"> {}
@Entity("connected_accounts")
export class ConnectedAccount extends BaseClass {
@@ -35,7 +35,7 @@ export class ConnectedAccount extends BaseClass {
type: string;
@Column()
- verifie: boolean;
+ verified: boolean;
@Column({ select: false })
visibility: number;
diff --git a/util/src/entities/Encryption.ts b/util/src/entities/Encryption.ts
new file mode 100644
index 00000000..3b82ff84
--- /dev/null
+++ b/util/src/entities/Encryption.ts
@@ -0,0 +1,35 @@
+import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { Guild } from "./Guild";
+import { PublicUserProjection, User } from "./User";
+import { HTTPError } from "lambert-server";
+import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util";
+import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField";
+import { Recipient } from "./Recipient";
+import { Message } from "./Message";
+import { ReadState } from "./ReadState";
+import { Invite } from "./Invite";
+import { DmChannelDTO } from "../dtos";
+
+@Entity("security_settings")
+export class SecuritySettings extends BaseClass {
+
+ @Column({nullable: true})
+ guild_id: Snowflake;
+
+ @Column({nullable: true})
+ channel_id: Snowflake;
+
+ @Column()
+ encryption_permission_mask: BitField;
+
+ @Column()
+ allowed_algorithms: string[];
+
+ @Column()
+ current_algorithm: string;
+
+ @Column({nullable: true})
+ used_since_message: Snowflake;
+
+}
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 9ac148ee..70bb41c5 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -187,11 +187,11 @@ export class Guild extends BaseClass {
@Column({ nullable: true })
@RelationId((guild: Guild) => guild.owner)
- owner_id: string;
+ owner_id?: string; // optional to allow for ownerless guilds
@JoinColumn({ name: "owner_id", referencedColumnName: "id" })
@ManyToOne(() => User)
- owner: User;
+ owner?: User; // optional to allow for ownerless guilds
@Column({ nullable: true })
preferred_locale?: string;
@@ -200,7 +200,7 @@ export class Guild extends BaseClass {
premium_subscription_count?: number;
@Column({ nullable: true })
- premium_tier?: number; // nitro boost level
+ premium_tier?: number; // crowd premium level
@Column({ nullable: true })
@RelationId((guild: Guild) => guild.public_updates_channel)
@@ -269,6 +269,10 @@ export class Guild extends BaseClass {
@Column({ nullable: true })
nsfw?: boolean;
+
+ // TODO: nested guilds
+ @Column({ nullable: true })
+ parent?: string;
// only for developer portal
permissions?: number;
@@ -308,7 +312,7 @@ export class Guild extends BaseClass {
verification_level: 0,
welcome_screen: {
enabled: false,
- description: "No description",
+ description: "Fill in your description",
welcome_channels: [],
},
widget_enabled: true, // NB: don't set it as false to prevent artificial restrictions
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index 3c5f9db0..fe2d5590 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -70,7 +70,7 @@ export class Member extends BaseClassWithoutId {
@Column({ nullable: true })
nick?: string;
-
+
@JoinTable({
name: "member_roles",
joinColumn: { name: "index", referencedColumnName: "index" },
@@ -85,8 +85,8 @@ export class Member extends BaseClassWithoutId {
@Column()
joined_at: Date;
- @Column({ nullable: true })
- premium_since?: Date;
+ @Column({ type: "bigint", nullable: true })
+ premium_since?: number;
@Column()
deaf: boolean;
@@ -102,8 +102,17 @@ export class Member extends BaseClassWithoutId {
@Column({ nullable: true })
last_message_id?: string;
+
+ /**
+ @JoinColumn({ name: "id" })
+ @ManyToOne(() => User, {
+ onDelete: "DO NOTHING",
+ // do not auto-kick force-joined members just because their joiners left the server
+ }) **/
+ @Column({ nullable: true})
+ joined_by?: string;
- // TODO: update
+ // TODO: add this when we have proper read receipts
// @Column({ type: "simple-json" })
// read_state: ReadState;
@@ -245,7 +254,7 @@ export class Member extends BaseClassWithoutId {
nick: undefined,
roles: [guild_id], // @everyone role
joined_at: new Date(),
- premium_since: new Date(),
+ premium_since: (new Date()).getTime(),
deaf: false,
mute: false,
pending: false,
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index e577d5df..b32bbd94 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -41,8 +41,14 @@ export enum MessageType {
CHANNEL_FOLLOW_ADD = 12,
GUILD_DISCOVERY_DISQUALIFIED = 14,
GUILD_DISCOVERY_REQUALIFIED = 15,
+ ENCRYPTED = 16,
REPLY = 19,
APPLICATION_COMMAND = 20,
+ ROUTE_ADDED = 41, // custom message routing: new route affecting that channel
+ ROUTE_DISABLED = 42, // custom message routing: given route no longer affecting that channel
+ ENCRYPTION = 50,
+ CUSTOM_START = 63,
+ UNHANDLED = 255
}
@Entity("messages")
@@ -84,7 +90,7 @@ export class Message extends BaseClass {
@RelationId((message: Message) => message.member)
member_id: string;
- @JoinColumn({ name: "author_id", referencedColumnName: "id" })
+ @JoinColumn({ name: "member_id", referencedColumnName: "id" })
@ManyToOne(() => User, {
onDelete: "CASCADE",
})
@@ -203,6 +209,7 @@ export interface MessageComponent {
}
export enum MessageComponentType {
+ Script = 0, // self command script
ActionRow = 1,
Button = 2,
}
diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index e6d73105..b915573b 100644
--- a/util/src/entities/ReadState.ts
+++ b/util/src/entities/ReadState.ts
@@ -49,6 +49,7 @@ export class ReadState extends BaseClass {
@Column({ nullable: true })
mention_count: number;
- @Column({ nullable: true })
+ // @Column({ nullable: true })
+ // TODO: derive this from (last_message_id=notifications_cursor=public_ack)=true
manual: boolean;
}
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 1d18c838..a5c4c136 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -60,7 +60,7 @@ export class User extends BaseClass {
username: string; // username max length 32, min 2 (should be configurable)
@Column()
- discriminator: string; // #0001 4 digit long string from #0001 - #9999
+ discriminator: string; // opaque string: 4 digits on discord.com
setDiscriminator(val: string) {
const number = Number(val);
@@ -88,10 +88,10 @@ export class User extends BaseClass {
mobile: boolean; // if the user has mobile app installed
@Column()
- premium: boolean; // if user bought nitro
-
+ premium: boolean; // if user bought individual premium
+
@Column()
- premium_type: number; // nitro level
+ premium_type: number; // individual premium level
@Column()
bot: boolean; // if user is bot
@@ -100,11 +100,11 @@ export class User extends BaseClass {
bio: string; // short description of the user (max 190 chars -> should be configurable)
@Column()
- system: boolean; // shouldn't be used, the api sents this field type true, if the generated message comes from a system generated author
+ system: boolean; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author
@Column({ select: false })
- nsfw_allowed: boolean; // if the user is older than 18 (resp. Config)
-
+ nsfw_allowed: boolean; // if the user can do age-restricted actions (NSFW channels/guilds/commands)
+
@Column({ select: false })
mfa_enabled: boolean; // if multi factor authentication is enabled
@@ -132,7 +132,7 @@ export class User extends BaseClass {
@Column()
public_flags: number;
- @Column()
+ @Column({ type: "bigint" })
rights: string; // Rights
@OneToMany(() => Session, (session: Session) => session.user)
@@ -164,6 +164,9 @@ export class User extends BaseClass {
@Column({ type: "simple-json", select: false })
settings: UserSettings;
+ @Column({ type: "simple-json" })
+ notes: { [key: string]: string }; //key is ID of user
+
toPublicUser() {
const user: any = {};
PublicUserProjection.forEach((x) => {
@@ -271,6 +274,7 @@ export class User extends BaseClass {
},
settings: { ...defaultSettings, locale: language },
fingerprints: [],
+ notes: {},
});
await user.save();
@@ -360,7 +364,7 @@ export interface UserSettings {
render_reactions: boolean;
restricted_guilds: string[];
show_current_game: boolean;
- status: "online" | "offline" | "dnd" | "idle";
+ status: "online" | "offline" | "dnd" | "idle" | "invisible";
stream_notifications_enabled: boolean;
theme: "dark" | "white"; // dark
timezone_offset: number; // e.g -60
diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index fc18d422..f023d5a6 100644
--- a/util/src/entities/index.ts
+++ b/util/src/entities/index.ts
@@ -27,4 +27,4 @@ export * from "./Template";
export * from "./User";
export * from "./VoiceState";
export * from "./Webhook";
-export * from "./ClientRelase";
\ No newline at end of file
+export * from "./ClientRelease";
\ No newline at end of file
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index a5253c09..416082ed 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -623,6 +623,7 @@ export type EVENT =
| "PRESENCE_UPDATE"
| "TYPING_START"
| "USER_UPDATE"
+ | "USER_NOTE_UPDATE"
| "WEBHOOKS_UPDATE"
| "INTERACTION_CREATE"
| "VOICE_STATE_UPDATE"
diff --git a/util/src/interfaces/Interaction.ts b/util/src/interfaces/Interaction.ts
index 3cafb2d5..5d3aae24 100644
--- a/util/src/interfaces/Interaction.ts
+++ b/util/src/interfaces/Interaction.ts
@@ -12,11 +12,13 @@ export interface Interaction {
}
export enum InteractionType {
+ SelfCommand = 0,
Ping = 1,
ApplicationCommand = 2,
}
export enum InteractionResponseType {
+ SelfCommandResponse = 0,
Pong = 1,
Acknowledge = 2,
ChannelMessage = 3,
diff --git a/util/src/interfaces/Status.ts b/util/src/interfaces/Status.ts
index c4dab586..5d2e1bba 100644
--- a/util/src/interfaces/Status.ts
+++ b/util/src/interfaces/Status.ts
@@ -1,4 +1,4 @@
-export type Status = "idle" | "dnd" | "online" | "offline";
+export type Status = "idle" | "dnd" | "online" | "offline" | "invisible";
export interface ClientStatus {
desktop?: string; // e.g. Windows/Linux/Mac
diff --git a/util/src/migrations/1648643945733-ReleaseTypo.ts b/util/src/migrations/1648643945733-ReleaseTypo.ts
new file mode 100644
index 00000000..944b9dd9
--- /dev/null
+++ b/util/src/migrations/1648643945733-ReleaseTypo.ts
@@ -0,0 +1,16 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class ReleaseTypo1648643945733 implements MigrationInterface {
+ name = "ReleaseTypo1648643945733";
+
+ public async up(queryRunner: QueryRunner): Promise<void> {
+ //drop table first because typeorm creates it before migrations run
+ await queryRunner.dropTable("client_release", true);
+ await queryRunner.renameTable("client_relase", "client_release");
+ }
+
+ public async down(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.dropTable("client_relase", true);
+ await queryRunner.renameTable("client_release", "client_relase");
+ }
+}
diff --git a/util/src/util/Rights.ts b/util/src/util/Rights.ts
index 9a99d393..35ad9514 100644
--- a/util/src/util/Rights.ts
+++ b/util/src/util/Rights.ts
@@ -1,6 +1,7 @@
import { BitField } from "./BitField";
import "missing-native-js-functions";
import { BitFieldResolvable, BitFlag } from "./BitField";
+import { User } from "../entities";
var HTTPError: any;
@@ -65,6 +66,8 @@ export class Rights extends BitField {
// inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user
SELF_ADD_DISCOVERABLE: BitFlag(36), // can mark discoverable guilds that they have permissions to mark as discoverable
MANAGE_GUILD_DIRECTORY: BitFlag(37), // can change anything in the primary guild directory
+ POGGERS: BitFlag(38), // can send confetti, screenshake, random user mention (@someone)
+ USE_ACHIEVEMENTS: BitFlag(39), // can use achievements and cheers
INITIATE_INTERACTIONS: BitFlag(40), // can initiate interactions
RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions
SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events
@@ -83,6 +86,15 @@ export class Rights extends BitField {
// @ts-ignore
throw new HTTPError(`You are missing the following rights ${permission}`, 403);
}
+
}
const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce((total, val) => total | val, BigInt(0));
+
+export async function getRights( user_id: string
+ /**, opts: {
+ in_behalf?: (keyof User)[];
+ } = {} **/) {
+ let user = await User.findOneOrFail({ where: { id: user_id } });
+ return new Rights(user.rights);
+}
diff --git a/util/src/util/TraverseDirectory.ts b/util/src/util/TraverseDirectory.ts
index 275b7dcc..3d0d6279 100644
--- a/util/src/util/TraverseDirectory.ts
+++ b/util/src/util/TraverseDirectory.ts
@@ -1,6 +1,9 @@
import { Server, traverseDirectory } from "lambert-server";
-const DEFAULT_FILTER = /^([^\.].*)(?<!\.d)\.(js)$/;
+//if we're using ts-node, use ts files instead of js
+const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js"
+
+const DEFAULT_FILTER = new RegExp("^([^\.].*)(?<!\.d)\.(" + extension + ")$");
export function registerRoutes(server: Server, root: string) {
return traverseDirectory(
|