mirror of
https://github.com/spacebarchat/server.git
synced 2026-05-11 12:34:51 +00:00
restructure to single project
This commit is contained in:
committed by
TheArcaneBrony
parent
5abd6bb7e0
commit
5e86d7ab9c
@@ -0,0 +1,22 @@
|
||||
import { ApiConfiguration, ClientConfiguration, DefaultsConfiguration, EndpointConfiguration, GeneralConfiguration, GifConfiguration, GuildConfiguration, KafkaConfiguration, LimitsConfiguration, LoginConfiguration, MetricsConfiguration, RabbitMQConfiguration, RegionConfiguration, RegisterConfiguration, SecurityConfiguration, SentryConfiguration, TemplateConfiguration } from ".";
|
||||
|
||||
export class ConfigValue {
|
||||
gateway: EndpointConfiguration = new EndpointConfiguration();
|
||||
cdn: EndpointConfiguration = new EndpointConfiguration();
|
||||
api: ApiConfiguration = new ApiConfiguration();
|
||||
general: GeneralConfiguration = new GeneralConfiguration();
|
||||
limits: LimitsConfiguration = new LimitsConfiguration();
|
||||
security: SecurityConfiguration = new SecurityConfiguration();
|
||||
login: LoginConfiguration = new LoginConfiguration();
|
||||
register: RegisterConfiguration = new RegisterConfiguration();
|
||||
regions: RegionConfiguration = new RegionConfiguration();
|
||||
guild: GuildConfiguration = new GuildConfiguration();
|
||||
gif: GifConfiguration = new GifConfiguration();
|
||||
rabbitmq: RabbitMQConfiguration = new RabbitMQConfiguration();
|
||||
kafka: KafkaConfiguration = new KafkaConfiguration();
|
||||
templates: TemplateConfiguration = new TemplateConfiguration();
|
||||
client: ClientConfiguration = new ClientConfiguration();
|
||||
metrics: MetricsConfiguration = new MetricsConfiguration();
|
||||
sentry: SentryConfiguration = new SentryConfiguration();
|
||||
defaults: DefaultsConfiguration = new DefaultsConfiguration();
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./Config";
|
||||
export * from "./types/index";
|
||||
@@ -0,0 +1,5 @@
|
||||
export class ApiConfiguration {
|
||||
defaultVersion: string = "9";
|
||||
activeVersions: string[] = ["6", "7", "8", "9"];
|
||||
useFosscordEnhancements: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ClientReleaseConfiguration } from ".";
|
||||
|
||||
export class ClientConfiguration {
|
||||
//classes
|
||||
releases: ClientReleaseConfiguration = new ClientReleaseConfiguration();
|
||||
//base types
|
||||
useTestClient: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { GuildDefaults, UserDefaults } from ".";
|
||||
|
||||
export class DefaultsConfiguration {
|
||||
guild: GuildDefaults = new GuildDefaults();
|
||||
user: UserDefaults = new UserDefaults();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class EndpointConfiguration {
|
||||
endpointClient: string | null = null;
|
||||
endpointPrivate: string | null = null;
|
||||
endpointPublic: string | null = null;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Snowflake } from "../../util";
|
||||
|
||||
export class GeneralConfiguration {
|
||||
instanceName: string = "Fosscord Instance";
|
||||
instanceDescription: string | null = "This is a Fosscord instance made in the pre-release days";
|
||||
frontPage: string | null = null;
|
||||
tosPage: string | null = null;
|
||||
correspondenceEmail: string | null = "noreply@localhost.local";
|
||||
correspondenceUserID: string | null = null;
|
||||
image: string | null = null;
|
||||
instanceId: string = Snowflake.generate();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class GifConfiguration {
|
||||
enabled: boolean = true;
|
||||
provider: "tenor" = "tenor"; // more coming soon
|
||||
apiKey?: string = "LIVDSRZULELA";
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { DiscoveryConfiguration, AutoJoinConfiguration } from ".";
|
||||
|
||||
export class GuildConfiguration {
|
||||
discovery: DiscoveryConfiguration = new DiscoveryConfiguration();
|
||||
autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { KafkaBroker } from ".";
|
||||
|
||||
export class KafkaConfiguration {
|
||||
brokers: KafkaBroker[] | null = null;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ChannelLimits, GuildLimits, MessageLimits, RateLimits, UserLimits } from ".";
|
||||
|
||||
export class LimitsConfiguration {
|
||||
user: UserLimits = new UserLimits();
|
||||
guild: GuildLimits = new GuildLimits();
|
||||
message: MessageLimits = new MessageLimits();
|
||||
channel: ChannelLimits = new ChannelLimits();
|
||||
rate: RateLimits = new RateLimits();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class LoginConfiguration {
|
||||
requireCaptcha: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class MetricsConfiguration {
|
||||
timeout: number = 30000;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class RabbitMQConfiguration {
|
||||
host: string | null = null;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Region } from ".";
|
||||
|
||||
export class RegionConfiguration {
|
||||
default: string = "fosscord";
|
||||
useDefaultAsOptimal: boolean = true;
|
||||
available: Region[] = [
|
||||
{
|
||||
id: "fosscord",
|
||||
name: "Fosscord",
|
||||
endpoint: "127.0.0.1:3004",
|
||||
vip: false,
|
||||
custom: false,
|
||||
deprecated: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { DateOfBirthConfiguration, EmailConfiguration, PasswordConfiguration } from ".";
|
||||
|
||||
export class RegisterConfiguration {
|
||||
//classes
|
||||
email: EmailConfiguration = new EmailConfiguration();
|
||||
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
||||
password: PasswordConfiguration = new PasswordConfiguration();
|
||||
//base types
|
||||
disabled: boolean = false;
|
||||
requireCaptcha: boolean = true;
|
||||
requireInvite: boolean = false;
|
||||
guestsRequireInvite: boolean = true;
|
||||
allowNewRegistration: boolean = true;
|
||||
allowMultipleAccounts: boolean = true;
|
||||
blockProxies: boolean = true;
|
||||
incrementingDiscriminators: boolean = false; // random otherwise
|
||||
defaultRights: string = "0";
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import crypto from "crypto";
|
||||
import { CaptchaConfiguration, TwoFactorConfiguration } from ".";
|
||||
|
||||
export class SecurityConfiguration {
|
||||
//classes
|
||||
captcha: CaptchaConfiguration = new CaptchaConfiguration();
|
||||
twoFactor: TwoFactorConfiguration = new TwoFactorConfiguration();
|
||||
//base types
|
||||
autoUpdate: boolean | number = true;
|
||||
requestSignature: string = crypto.randomBytes(32).toString("base64");
|
||||
jwtSecret: string = crypto.randomBytes(256).toString("base64");
|
||||
// header to get the real user ip address
|
||||
// X-Forwarded-For for nginx/reverse proxies
|
||||
// CF-Connecting-IP for cloudflare
|
||||
forwadedFor: string | null = null;
|
||||
ipdataApiKey: string | null = "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { hostname } from "os";
|
||||
|
||||
export class SentryConfiguration {
|
||||
enabled: boolean = false;
|
||||
endpoint: string = "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6";
|
||||
traceSampleRate: number = 1.0;
|
||||
environment: string = hostname();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class TemplateConfiguration {
|
||||
enabled: boolean = true;
|
||||
allowTemplateCreation: boolean = true;
|
||||
allowDiscordTemplates: boolean = true;
|
||||
allowRaws: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export * from "./ApiConfiguration";
|
||||
export * from "./ClientConfiguration";
|
||||
export * from "./DefaultsConfiguration";
|
||||
export * from "./EndpointConfiguration";
|
||||
export * from "./GeneralConfiguration";
|
||||
export * from "./GifConfiguration";
|
||||
export * from "./GuildConfiguration";
|
||||
export * from "./KafkaConfiguration";
|
||||
export * from "./LimitConfigurations";
|
||||
export * from "./LoginConfiguration";
|
||||
export * from "./MetricsConfiguration";
|
||||
export * from "./RabbitMQConfiguration";
|
||||
export * from "./RegionConfiguration";
|
||||
export * from "./RegisterConfiguration";
|
||||
export * from "./SecurityConfiguration";
|
||||
export * from "./SentryConfiguration";
|
||||
export * from "./TemplateConfiguration";
|
||||
export * from "./subconfigurations/index";
|
||||
@@ -0,0 +1,4 @@
|
||||
export class ClientReleaseConfiguration {
|
||||
useLocalRelease: boolean = true; //TODO
|
||||
upstreamVersion: string = "0.0.264";
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ClientReleaseConfiguration";
|
||||
@@ -0,0 +1,8 @@
|
||||
export class GuildDefaults {
|
||||
maxPresences: number = 250000;
|
||||
maxVideoChannelUsers: number = 200;
|
||||
afkTimeout: number = 300;
|
||||
defaultMessageNotifications: number = 1;
|
||||
explicitContentFilter: number = 0;
|
||||
test: number = 123;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class UserDefaults {
|
||||
premium: boolean = false;
|
||||
premium_type: number = 2;
|
||||
verified: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./GuildDefaults";
|
||||
export * from "./UserDefaults";
|
||||
@@ -0,0 +1,5 @@
|
||||
export class AutoJoinConfiguration {
|
||||
enabled: boolean = true;
|
||||
guilds: string[] = [];
|
||||
canLeave: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class DiscoveryConfiguration {
|
||||
showAllGuilds: boolean = false;
|
||||
useRecommendation: boolean = false; // TODO: Recommendation, privacy concern?
|
||||
offset: number = 0;
|
||||
limit: number = 24;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./AutoJoin";
|
||||
export * from "./Discovery";
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from "./client/index";
|
||||
export * from "./defaults/index";
|
||||
export * from "./guild/index";
|
||||
export * from "./kafka/index";
|
||||
export * from "./limits/index";
|
||||
export * from "./region/index";
|
||||
export * from "./register/index";
|
||||
export * from "./security/index";
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface KafkaBroker {
|
||||
ip: string;
|
||||
port: number;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./KafkaBroker";
|
||||
@@ -0,0 +1,5 @@
|
||||
export class ChannelLimits {
|
||||
maxPins: number = 500;
|
||||
maxTopic: number = 1024;
|
||||
maxWebhooks: number = 100;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export class GuildLimits {
|
||||
maxRoles: number = 1000;
|
||||
maxEmojis: number = 2000;
|
||||
maxMembers: number = 25000000;
|
||||
maxChannels: number = 65535;
|
||||
maxChannelsInCategory: number = 65535;
|
||||
hideOfflineMember: number = 3;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export class MessageLimits {
|
||||
maxCharacters: number = 1048576;
|
||||
maxTTSCharacters: number = 160;
|
||||
maxReactions: number = 2048;
|
||||
maxAttachmentSize: number = 1024 * 1024 * 1024;
|
||||
maxBulkDelete: number = 1000;
|
||||
maxEmbedDownloadSize: number = 1024 * 1024 * 5;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { RouteRateLimit, RateLimitOptions } from ".";
|
||||
|
||||
export class RateLimits {
|
||||
disabled: boolean = true;
|
||||
ip: Omit<RateLimitOptions, "bot_count"> = {
|
||||
count: 500,
|
||||
window: 5
|
||||
};
|
||||
global: RateLimitOptions = {
|
||||
count: 250,
|
||||
window: 5
|
||||
};
|
||||
error: RateLimitOptions = {
|
||||
count: 10,
|
||||
window: 5
|
||||
};
|
||||
routes: RouteRateLimit;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class UserLimits {
|
||||
maxGuilds: number = 1048576;
|
||||
maxUsername: number = 127;
|
||||
maxFriends: number = 5000;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from "./ChannelLimits";
|
||||
export * from "./GuildLimits";
|
||||
export * from "./MessageLimits";
|
||||
export * from "./RateLimits";
|
||||
export * from "./UserLimits";
|
||||
export * from "./ratelimits/index";
|
||||
@@ -0,0 +1,12 @@
|
||||
import { RateLimitOptions } from "./RateLimitOptions";
|
||||
|
||||
export class AuthRateLimit {
|
||||
login: RateLimitOptions = {
|
||||
count: 5,
|
||||
window: 60
|
||||
};
|
||||
register: RateLimitOptions = {
|
||||
count: 2,
|
||||
window: 60 * 60 * 12
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface RateLimitOptions {
|
||||
bot?: number;
|
||||
count: number;
|
||||
window: number;
|
||||
onyIp?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { AuthRateLimit } from "./Auth";
|
||||
import { RateLimitOptions } from "./RateLimitOptions";
|
||||
|
||||
export class RouteRateLimit {
|
||||
guild: RateLimitOptions = {
|
||||
count: 5,
|
||||
window: 5
|
||||
};
|
||||
webhook: RateLimitOptions = {
|
||||
count: 10,
|
||||
window: 5
|
||||
};
|
||||
channel: RateLimitOptions = {
|
||||
count: 10,
|
||||
window: 5
|
||||
};
|
||||
auth: AuthRateLimit;
|
||||
// TODO: rate limit configuration for all routes
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./Auth";
|
||||
export * from "./RateLimitOptions";
|
||||
export * from "./Route";
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface Region {
|
||||
id: string;
|
||||
name: string;
|
||||
endpoint: string;
|
||||
location?: {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
vip: boolean;
|
||||
custom: boolean;
|
||||
deprecated: boolean;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./Region";
|
||||
@@ -0,0 +1,4 @@
|
||||
export class DateOfBirthConfiguration {
|
||||
required: boolean = true;
|
||||
minimum: number = 13; // in years
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class EmailConfiguration {
|
||||
required: boolean = false;
|
||||
allowlist: boolean = false;
|
||||
blocklist: boolean = true;
|
||||
domains: string[] = [];// TODO: efficiently save domain blocklist in database
|
||||
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class PasswordConfiguration {
|
||||
required: boolean = false;
|
||||
minLength: number = 8;
|
||||
minNumbers: number = 2;
|
||||
minUpperCase: number =2;
|
||||
minSymbols: number = 0;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./DateOfBirth";
|
||||
export * from "./Email";
|
||||
export * from "./Password";
|
||||
@@ -0,0 +1,6 @@
|
||||
export class CaptchaConfiguration {
|
||||
enabled: boolean = false;
|
||||
service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom
|
||||
sitekey: string | null = null;
|
||||
secret: string | null = null;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class TwoFactorConfiguration {
|
||||
generateBackupCodes: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./Captcha";
|
||||
export * from "./TwoFactor";
|
||||
@@ -0,0 +1,41 @@
|
||||
import { MinimalPublicUserDTO } from "./UserDTO";
|
||||
import { Channel, PublicUserProjection, User } from "../entities";
|
||||
|
||||
export class DmChannelDTO {
|
||||
icon: string | null;
|
||||
id: string;
|
||||
last_message_id: string | null;
|
||||
name: string | null;
|
||||
origin_channel_id: string | null;
|
||||
owner_id?: string;
|
||||
recipients: MinimalPublicUserDTO[];
|
||||
type: number;
|
||||
|
||||
static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) {
|
||||
const obj = new DmChannelDTO();
|
||||
obj.icon = channel.icon || null;
|
||||
obj.id = channel.id;
|
||||
obj.last_message_id = channel.last_message_id || null;
|
||||
obj.name = channel.name || null;
|
||||
obj.origin_channel_id = origin_channel_id || null;
|
||||
obj.owner_id = channel.owner_id;
|
||||
obj.type = channel.type;
|
||||
obj.recipients = (
|
||||
await Promise.all(
|
||||
channel
|
||||
.recipients!.filter((r) => !excluded_recipients.includes(r.user_id))
|
||||
.map(async (r) => {
|
||||
return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection });
|
||||
})
|
||||
)
|
||||
).map((u) => new MinimalPublicUserDTO(u));
|
||||
return obj;
|
||||
}
|
||||
|
||||
excludedRecipients(excluded_recipients: string[]): DmChannelDTO {
|
||||
return {
|
||||
...this,
|
||||
recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { User } from "../entities";
|
||||
|
||||
export class MinimalPublicUserDTO {
|
||||
avatar?: string | null;
|
||||
discriminator: string;
|
||||
id: string;
|
||||
public_flags: number;
|
||||
username: string;
|
||||
|
||||
constructor(user: User) {
|
||||
this.avatar = user.avatar;
|
||||
this.discriminator = user.discriminator;
|
||||
this.id = user.id;
|
||||
this.public_flags = user.public_flags;
|
||||
this.username = user.username;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./DmChannelDTO";
|
||||
export * from "./UserDTO";
|
||||
@@ -0,0 +1,156 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { Team } from "./Team";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity("applications")
|
||||
export class Application extends BaseClass {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
summary: string = "";
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
type?: any;
|
||||
|
||||
@Column()
|
||||
hook: boolean = true;
|
||||
|
||||
@Column()
|
||||
bot_public?: boolean = true;
|
||||
|
||||
@Column()
|
||||
bot_require_code_grant?: boolean = false;
|
||||
|
||||
@Column()
|
||||
verify_key: string;
|
||||
|
||||
@JoinColumn({ name: "owner_id" })
|
||||
@ManyToOne(() => User)
|
||||
owner: User;
|
||||
|
||||
@Column()
|
||||
flags: number = 0;
|
||||
|
||||
@Column({ type: "simple-array", nullable: true })
|
||||
redirect_uris: string[] = [];
|
||||
|
||||
@Column({ nullable: true })
|
||||
rpc_application_state: number = 0;
|
||||
|
||||
@Column({ nullable: true })
|
||||
store_application_state: number = 1;
|
||||
|
||||
@Column({ nullable: true })
|
||||
verification_state: number = 1;
|
||||
|
||||
@Column({ nullable: true })
|
||||
interactions_endpoint_url?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
integration_public: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
integration_require_code_grant: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
discoverability_state: number = 1;
|
||||
|
||||
@Column({ nullable: true })
|
||||
discovery_eligibility_flags: number = 2240;
|
||||
|
||||
@JoinColumn({ name: "bot_user_id" })
|
||||
@OneToOne(() => User)
|
||||
bot?: User;
|
||||
|
||||
@Column({ type: "simple-array", nullable: true })
|
||||
tags?: string[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
cover_image?: string; // the application's default rich presence invite cover image hash
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
install_params?: {scopes: string[], permissions: string};
|
||||
|
||||
@Column({ nullable: true })
|
||||
terms_of_service_url?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
privacy_policy_url?: string;
|
||||
|
||||
//just for us
|
||||
|
||||
//@Column({ type: "simple-array", nullable: true })
|
||||
//rpc_origins?: string[];
|
||||
|
||||
//@JoinColumn({ name: "guild_id" })
|
||||
//@ManyToOne(() => Guild)
|
||||
//guild?: Guild; // if this application is a game sold, this field will be the guild to which it has been linked
|
||||
|
||||
//@Column({ nullable: true })
|
||||
//primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created,
|
||||
|
||||
//@Column({ nullable: true })
|
||||
//slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page
|
||||
|
||||
@JoinColumn({ name: "team_id" })
|
||||
@ManyToOne(() => Team, {
|
||||
onDelete: "CASCADE",
|
||||
nullable: true
|
||||
})
|
||||
team?: Team;
|
||||
|
||||
}
|
||||
|
||||
export interface ApplicationCommand {
|
||||
id: string;
|
||||
application_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
options?: ApplicationCommandOption[];
|
||||
}
|
||||
|
||||
export interface ApplicationCommandOption {
|
||||
type: ApplicationCommandOptionType;
|
||||
name: string;
|
||||
description: string;
|
||||
required?: boolean;
|
||||
choices?: ApplicationCommandOptionChoice[];
|
||||
options?: ApplicationCommandOption[];
|
||||
}
|
||||
|
||||
export interface ApplicationCommandOptionChoice {
|
||||
name: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export enum ApplicationCommandOptionType {
|
||||
SUB_COMMAND = 1,
|
||||
SUB_COMMAND_GROUP = 2,
|
||||
STRING = 3,
|
||||
INTEGER = 4,
|
||||
BOOLEAN = 5,
|
||||
USER = 6,
|
||||
CHANNEL = 7,
|
||||
ROLE = 8,
|
||||
}
|
||||
|
||||
export interface ApplicationCommandInteractionData {
|
||||
id: string;
|
||||
name: string;
|
||||
options?: ApplicationCommandInteractionDataOption[];
|
||||
}
|
||||
|
||||
export interface ApplicationCommandInteractionDataOption {
|
||||
name: string;
|
||||
value?: any;
|
||||
options?: ApplicationCommandInteractionDataOption[];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { URL } from "url";
|
||||
import { deleteFile } from "../util/cdn";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
|
||||
@Entity("attachments")
|
||||
export class Attachment extends BaseClass {
|
||||
@Column()
|
||||
filename: string; // name of file attached
|
||||
|
||||
@Column()
|
||||
size: number; // size of file in bytes
|
||||
|
||||
@Column()
|
||||
url: string; // source url of file
|
||||
|
||||
@Column()
|
||||
proxy_url: string; // a proxied url of file
|
||||
|
||||
@Column({ nullable: true })
|
||||
height?: number; // height of file (if image)
|
||||
|
||||
@Column({ nullable: true })
|
||||
width?: number; // width of file (if image)
|
||||
|
||||
@Column({ nullable: true })
|
||||
content_type?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((attachment: Attachment) => attachment.message)
|
||||
message_id: string;
|
||||
|
||||
@JoinColumn({ name: "message_id" })
|
||||
@ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
message: import("./Message").Message;
|
||||
|
||||
@BeforeRemove()
|
||||
onDelete() {
|
||||
return deleteFile(new URL(this.url).pathname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { ChannelPermissionOverwrite } from "./Channel";
|
||||
import { User } from "./User";
|
||||
|
||||
export enum AuditLogEvents {
|
||||
// guild level
|
||||
GUILD_UPDATE = 1,
|
||||
GUILD_IMPORT = 2,
|
||||
GUILD_EXPORTED = 3,
|
||||
GUILD_ARCHIVE = 4,
|
||||
GUILD_UNARCHIVE = 5,
|
||||
// join-leave
|
||||
USER_JOIN = 6,
|
||||
USER_LEAVE = 7,
|
||||
// channels
|
||||
CHANNEL_CREATE = 10,
|
||||
CHANNEL_UPDATE = 11,
|
||||
CHANNEL_DELETE = 12,
|
||||
// permission overrides
|
||||
CHANNEL_OVERWRITE_CREATE = 13,
|
||||
CHANNEL_OVERWRITE_UPDATE = 14,
|
||||
CHANNEL_OVERWRITE_DELETE = 15,
|
||||
// kick and ban
|
||||
MEMBER_KICK = 20,
|
||||
MEMBER_PRUNE = 21,
|
||||
MEMBER_BAN_ADD = 22,
|
||||
MEMBER_BAN_REMOVE = 23,
|
||||
// member updates
|
||||
MEMBER_UPDATE = 24,
|
||||
MEMBER_ROLE_UPDATE = 25,
|
||||
MEMBER_MOVE = 26,
|
||||
MEMBER_DISCONNECT = 27,
|
||||
BOT_ADD = 28,
|
||||
// roles
|
||||
ROLE_CREATE = 30,
|
||||
ROLE_UPDATE = 31,
|
||||
ROLE_DELETE = 32,
|
||||
ROLE_SWAP = 33,
|
||||
// invites
|
||||
INVITE_CREATE = 40,
|
||||
INVITE_UPDATE = 41,
|
||||
INVITE_DELETE = 42,
|
||||
// webhooks
|
||||
WEBHOOK_CREATE = 50,
|
||||
WEBHOOK_UPDATE = 51,
|
||||
WEBHOOK_DELETE = 52,
|
||||
WEBHOOK_SWAP = 53,
|
||||
// custom emojis
|
||||
EMOJI_CREATE = 60,
|
||||
EMOJI_UPDATE = 61,
|
||||
EMOJI_DELETE = 62,
|
||||
EMOJI_SWAP = 63,
|
||||
// deletion
|
||||
MESSAGE_CREATE = 70, // messages sent using non-primary seat of the user only
|
||||
MESSAGE_EDIT = 71, // non-self edits only
|
||||
MESSAGE_DELETE = 72,
|
||||
MESSAGE_BULK_DELETE = 73,
|
||||
// pinning
|
||||
MESSAGE_PIN = 74,
|
||||
MESSAGE_UNPIN = 75,
|
||||
// integrations
|
||||
INTEGRATION_CREATE = 80,
|
||||
INTEGRATION_UPDATE = 81,
|
||||
INTEGRATION_DELETE = 82,
|
||||
// stage actions
|
||||
STAGE_INSTANCE_CREATE = 83,
|
||||
STAGE_INSTANCE_UPDATE = 84,
|
||||
STAGE_INSTANCE_DELETE = 85,
|
||||
// stickers
|
||||
STICKER_CREATE = 90,
|
||||
STICKER_UPDATE = 91,
|
||||
STICKER_DELETE = 92,
|
||||
STICKER_SWAP = 93,
|
||||
// threads
|
||||
THREAD_CREATE = 110,
|
||||
THREAD_UPDATE = 111,
|
||||
THREAD_DELETE = 112,
|
||||
// application commands
|
||||
APPLICATION_COMMAND_PERMISSION_UPDATE = 121,
|
||||
// automod
|
||||
POLICY_CREATE = 140,
|
||||
POLICY_UPDATE = 141,
|
||||
POLICY_DELETE = 142,
|
||||
MESSAGE_BLOCKED_BY_POLICIES = 143, // in fosscord, blocked messages are stealth-dropped
|
||||
// instance policies affecting the guild
|
||||
GUILD_AFFECTED_BY_POLICIES = 216,
|
||||
// message moves
|
||||
IN_GUILD_MESSAGE_MOVE = 223,
|
||||
CROSS_GUILD_MESSAGE_MOVE = 224,
|
||||
// message routing
|
||||
ROUTE_CREATE = 225,
|
||||
ROUTE_UPDATE = 226,
|
||||
}
|
||||
|
||||
@Entity("audit_logs")
|
||||
export class AuditLog extends BaseClass {
|
||||
@JoinColumn({ name: "target_id" })
|
||||
@ManyToOne(() => User)
|
||||
target?: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((auditlog: AuditLog) => auditlog.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, (user: User) => user.id)
|
||||
user: User;
|
||||
|
||||
@Column({ type: "int" })
|
||||
action_type: AuditLogEvents;
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
options?: {
|
||||
delete_member_days?: string;
|
||||
members_removed?: string;
|
||||
channel_id?: string;
|
||||
messaged_id?: string;
|
||||
count?: string;
|
||||
id?: string;
|
||||
type?: string;
|
||||
role_name?: string;
|
||||
};
|
||||
|
||||
@Column()
|
||||
@Column({ type: "simple-json" })
|
||||
changes: AuditLogChange[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface AuditLogChange {
|
||||
new_value?: AuditLogChangeValue;
|
||||
old_value?: AuditLogChangeValue;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface AuditLogChangeValue {
|
||||
name?: string;
|
||||
description?: string;
|
||||
icon_hash?: string;
|
||||
splash_hash?: string;
|
||||
discovery_splash_hash?: string;
|
||||
banner_hash?: string;
|
||||
owner_id?: string;
|
||||
region?: string;
|
||||
preferred_locale?: string;
|
||||
afk_channel_id?: string;
|
||||
afk_timeout?: number;
|
||||
rules_channel_id?: string;
|
||||
public_updates_channel_id?: string;
|
||||
mfa_level?: number;
|
||||
verification_level?: number;
|
||||
explicit_content_filter?: number;
|
||||
default_message_notifications?: number;
|
||||
vanity_url_code?: string;
|
||||
$add?: {}[];
|
||||
$remove?: {}[];
|
||||
prune_delete_days?: number;
|
||||
widget_enabled?: boolean;
|
||||
widget_channel_id?: string;
|
||||
system_channel_id?: string;
|
||||
position?: number;
|
||||
topic?: string;
|
||||
bitrate?: number;
|
||||
permission_overwrites?: ChannelPermissionOverwrite[];
|
||||
nsfw?: boolean;
|
||||
application_id?: string;
|
||||
rate_limit_per_user?: number;
|
||||
permissions?: string;
|
||||
color?: number;
|
||||
hoist?: boolean;
|
||||
mentionable?: boolean;
|
||||
allow?: string;
|
||||
deny?: string;
|
||||
code?: string;
|
||||
channel_id?: string;
|
||||
inviter_id?: string;
|
||||
max_uses?: number;
|
||||
uses?: number;
|
||||
max_age?: number;
|
||||
temporary?: boolean;
|
||||
deaf?: boolean;
|
||||
mute?: boolean;
|
||||
nick?: string;
|
||||
avatar_hash?: string;
|
||||
id?: string;
|
||||
type?: number;
|
||||
enable_emoticons?: boolean;
|
||||
expire_behavior?: number;
|
||||
expire_grace_period?: number;
|
||||
user_limit?: number;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity("backup_codes")
|
||||
export class BackupCode extends BaseClass {
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, { onDelete: "CASCADE" })
|
||||
user: User;
|
||||
|
||||
@Column()
|
||||
code: string;
|
||||
|
||||
@Column()
|
||||
consumed: boolean;
|
||||
|
||||
@Column()
|
||||
expired: boolean;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity("bans")
|
||||
export class Ban extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
@RelationId((ban: Ban) => ban.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((ban: Ban) => ban.guild)
|
||||
guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((ban: Ban) => ban.executor)
|
||||
executor_id: string;
|
||||
|
||||
@JoinColumn({ name: "executor_id" })
|
||||
@ManyToOne(() => User)
|
||||
executor: User;
|
||||
|
||||
@Column()
|
||||
ip: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
reason?: string;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import "reflect-metadata";
|
||||
import { BaseEntity, ObjectIdColumn, PrimaryColumn, SaveOptions } from "typeorm";
|
||||
import { Snowflake } from "../util/Snowflake";
|
||||
|
||||
export class BaseClassWithoutId extends BaseEntity {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn;
|
||||
|
||||
export class BaseClass extends BaseClassWithoutId {
|
||||
@PrimaryIdColumn()
|
||||
id: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!this.id) this.id = Snowflake.generate();
|
||||
}
|
||||
|
||||
save(options?: SaveOptions | undefined): Promise<this> {
|
||||
if (!this.id) this.id = Snowflake.generate();
|
||||
return super.save(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { PrimaryColumn, Column, Entity} from "typeorm";
|
||||
import { BaseClassWithoutId } from "./BaseClass";
|
||||
|
||||
// TODO: categories:
|
||||
// [{
|
||||
// "id": 16,
|
||||
// "default": "Anime & Manga",
|
||||
// "localizations": {
|
||||
// "de": "Anime & Manga",
|
||||
// "fr": "Anim\u00e9s et mangas",
|
||||
// "ru": "\u0410\u043d\u0438\u043c\u0435 \u0438 \u043c\u0430\u043d\u0433\u0430"
|
||||
// }
|
||||
// },
|
||||
// "is_primary": false/true
|
||||
// }]
|
||||
// Also populate discord default categories
|
||||
|
||||
@Entity("categories")
|
||||
export class Categories extends BaseClassWithoutId { // Not using snowflake
|
||||
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
name: string;
|
||||
|
||||
@Column({ type: "simple-json" })
|
||||
localizations: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
is_primary: boolean;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
|
||||
import { OrmUtils } from "../util/imports/OrmUtils";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { PublicUserProjection, User } from "./User";
|
||||
import { HTTPError } from "../util/imports/HTTPError";
|
||||
import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util";
|
||||
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
|
||||
import { Recipient } from "./Recipient";
|
||||
import { Message } from "./Message";
|
||||
import { ReadState } from "./ReadState";
|
||||
import { Invite } from "./Invite";
|
||||
import { VoiceState } from "./VoiceState";
|
||||
import { Webhook } from "./Webhook";
|
||||
import { DmChannelDTO } from "../dtos";
|
||||
|
||||
export enum ChannelType {
|
||||
GUILD_TEXT = 0, // a text channel within a guild
|
||||
DM = 1, // a direct message between users
|
||||
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 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
|
||||
DIRECTORY = 14, // guild directory listing channel
|
||||
GUILD_FORUM = 15, // forum composed of IM threads
|
||||
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
|
||||
}
|
||||
|
||||
@Entity("channels")
|
||||
export class Channel extends BaseClass {
|
||||
@Column()
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
name?: string;
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
icon?: string | null;
|
||||
|
||||
@Column({ type: "int" })
|
||||
type: ChannelType;
|
||||
|
||||
@OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
recipients?: Recipient[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
last_message_id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((channel: Channel) => channel.guild)
|
||||
guild_id?: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((channel: Channel) => channel.parent)
|
||||
parent_id: string;
|
||||
|
||||
@JoinColumn({ name: "parent_id" })
|
||||
@ManyToOne(() => Channel)
|
||||
parent?: Channel;
|
||||
|
||||
// for group DMs and owned custom channel types
|
||||
@Column({ nullable: true })
|
||||
@RelationId((channel: Channel) => channel.owner)
|
||||
owner_id: string;
|
||||
|
||||
@JoinColumn({ name: "owner_id" })
|
||||
@ManyToOne(() => User)
|
||||
owner: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
last_pin_timestamp?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
default_auto_archive_duration?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
position?: number;
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
permission_overwrites?: ChannelPermissionOverwrite[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
video_quality_mode?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
bitrate?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
user_limit?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
nsfw?: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
rate_limit_per_user?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
topic?: string;
|
||||
|
||||
@OneToMany(() => Invite, (invite: Invite) => invite.channel, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
invites?: Invite[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
retention_policy_id?: string;
|
||||
|
||||
@OneToMany(() => Message, (message: Message) => message.channel, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
messages?: Message[];
|
||||
|
||||
@OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
voice_states?: VoiceState[];
|
||||
|
||||
@OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
read_states?: ReadState[];
|
||||
|
||||
@OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
webhooks?: Webhook[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
flags?: number = 0;
|
||||
|
||||
@Column({ nullable: true })
|
||||
default_thread_rate_limit_per_user?: number = 0;
|
||||
|
||||
|
||||
// TODO: DM channel
|
||||
static async createChannel(
|
||||
channel: Partial<Channel>,
|
||||
user_id: string = "0",
|
||||
opts?: {
|
||||
keepId?: boolean;
|
||||
skipExistsCheck?: boolean;
|
||||
skipPermissionCheck?: boolean;
|
||||
skipEventEmit?: boolean;
|
||||
skipNameChecks?: boolean;
|
||||
}
|
||||
) {
|
||||
if (!opts?.skipPermissionCheck) {
|
||||
// Always check if user has permission first
|
||||
const permissions = await getPermission(user_id, channel.guild_id);
|
||||
permissions.hasThrow("MANAGE_CHANNELS");
|
||||
}
|
||||
|
||||
if (!opts?.skipNameChecks) {
|
||||
const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } });
|
||||
if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) {
|
||||
for (let character of InvisibleCharacters)
|
||||
if (channel.name.includes(character))
|
||||
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.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-")
|
||||
throw new HTTPError("Channel name cannot start/end with dash.", 403);
|
||||
}
|
||||
|
||||
if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) {
|
||||
if (!channel.name) throw new HTTPError("Channel name cannot be empty.", 403);
|
||||
}
|
||||
}
|
||||
|
||||
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({ 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");
|
||||
}
|
||||
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");
|
||||
case ChannelType.GUILD_STORE:
|
||||
default:
|
||||
throw new HTTPError("Not yet supported");
|
||||
}
|
||||
|
||||
if (!channel.permission_overwrites) channel.permission_overwrites = [];
|
||||
// TODO: eagerly auto generate position of all guild channels
|
||||
|
||||
channel = {
|
||||
...channel,
|
||||
...(!opts?.keepId && { id: Snowflake.generate() }),
|
||||
created_at: new Date(),
|
||||
position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0,
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
OrmUtils.mergeDeep(new Channel(), channel).save(),
|
||||
!opts?.skipEventEmit
|
||||
? emitEvent({
|
||||
event: "CHANNEL_CREATE",
|
||||
data: channel,
|
||||
guild_id: channel.guild_id,
|
||||
} as ChannelCreateEvent)
|
||||
: Promise.resolve(),
|
||||
]);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
|
||||
recipients = recipients.unique().filter((x) => x !== creator_user_id);
|
||||
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.GROUP_DM : ChannelType.DM;
|
||||
|
||||
let channel = null;
|
||||
|
||||
const channelRecipients = [...recipients, creator_user_id];
|
||||
|
||||
const userRecipients = await Recipient.find({
|
||||
where: { user_id: creator_user_id },
|
||||
relations: ["channel", "channel.recipients"],
|
||||
});
|
||||
|
||||
for (let ur of userRecipients) {
|
||||
let re = ur.channel.recipients!.map((r) => r.user_id);
|
||||
if (re.length === channelRecipients.length) {
|
||||
if (containsAll(re, channelRecipients)) {
|
||||
if (channel == null) {
|
||||
channel = ur.channel;
|
||||
ur = OrmUtils.mergeDeep(ur, { closed: false });
|
||||
await ur.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (channel == null) {
|
||||
name = trimSpecial(name);
|
||||
|
||||
channel = await (
|
||||
OrmUtils.mergeDeep(new Channel(), {
|
||||
name,
|
||||
type,
|
||||
owner_id: type === ChannelType.DM ? undefined : null, // 1:1 DMs are ownerless in fosscord-server
|
||||
created_at: new Date(),
|
||||
last_message_id: null,
|
||||
recipients: channelRecipients.map((x) =>
|
||||
OrmUtils.mergeDeep(new Recipient(), {
|
||||
user_id: x,
|
||||
closed: !(type === ChannelType.GROUP_DM || x === creator_user_id),
|
||||
})
|
||||
),
|
||||
}) as Channel
|
||||
).save();
|
||||
}
|
||||
|
||||
const channel_dto = await DmChannelDTO.from(channel);
|
||||
|
||||
if (type === ChannelType.GROUP_DM) {
|
||||
for (let recipient of channel.recipients!) {
|
||||
await emitEvent({
|
||||
event: "CHANNEL_CREATE",
|
||||
data: channel_dto.excludedRecipients([recipient.user_id]),
|
||||
user_id: recipient.user_id,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: 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) {
|
||||
await Recipient.delete({ channel_id: channel.id, user_id: user_id });
|
||||
channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id);
|
||||
|
||||
if (channel.recipients?.length === 0) {
|
||||
await Channel.deleteChannel(channel);
|
||||
await emitEvent({
|
||||
event: "CHANNEL_DELETE",
|
||||
data: await DmChannelDTO.from(channel, [user_id]),
|
||||
user_id: user_id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await emitEvent({
|
||||
event: "CHANNEL_DELETE",
|
||||
data: await DmChannelDTO.from(channel, [user_id]),
|
||||
user_id: user_id,
|
||||
});
|
||||
|
||||
//If the owner leave the server user is the new owner
|
||||
if (channel.owner_id === user_id) {
|
||||
channel.owner_id = "1"; // The channel is now owned by the server user
|
||||
await emitEvent({
|
||||
event: "CHANNEL_UPDATE",
|
||||
data: await DmChannelDTO.from(channel, [user_id]),
|
||||
channel_id: channel.id,
|
||||
});
|
||||
}
|
||||
|
||||
await channel.save();
|
||||
|
||||
await emitEvent({
|
||||
event: "CHANNEL_RECIPIENT_REMOVE",
|
||||
data: {
|
||||
channel_id: channel.id,
|
||||
user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }),
|
||||
},
|
||||
channel_id: channel.id,
|
||||
} as ChannelRecipientRemoveEvent);
|
||||
}
|
||||
|
||||
static async deleteChannel(channel: Channel) {
|
||||
await Message.delete({ channel_id: channel.id }); //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util
|
||||
//TODO before deleting the channel we should check and delete other relations
|
||||
await Channel.delete({ id: channel.id });
|
||||
}
|
||||
|
||||
isDm() {
|
||||
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,
|
||||
];
|
||||
return disallowedChannelTypes.indexOf(this.type) == -1;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChannelPermissionOverwrite {
|
||||
allow: string;
|
||||
deny: string;
|
||||
id: string;
|
||||
type: ChannelPermissionOverwriteType;
|
||||
}
|
||||
|
||||
export enum ChannelPermissionOverwriteType {
|
||||
role = 0,
|
||||
member = 1,
|
||||
group = 2,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Column, Entity} from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
|
||||
@Entity("client_release")
|
||||
export class Release extends BaseClass {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
pub_date: string;
|
||||
|
||||
@Column()
|
||||
url: string;
|
||||
|
||||
@Column()
|
||||
deb_url: string;
|
||||
|
||||
@Column()
|
||||
osx_url: string;
|
||||
|
||||
@Column()
|
||||
win_url: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
notes?: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Column, Entity } from "typeorm";
|
||||
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
|
||||
|
||||
@Entity("config")
|
||||
export class ConfigEntity extends BaseClassWithoutId {
|
||||
@PrimaryIdColumn()
|
||||
key: string;
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
value: number | boolean | null | string | undefined;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { User } from "./User";
|
||||
|
||||
export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verified"> {}
|
||||
|
||||
@Entity("connected_accounts")
|
||||
export class ConnectedAccount extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
@RelationId((account: ConnectedAccount) => account.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
|
||||
@Column({ select: false })
|
||||
access_token: string;
|
||||
|
||||
@Column({ select: false })
|
||||
friend_sync: boolean;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ select: false })
|
||||
revoked: boolean;
|
||||
|
||||
@Column({ select: false })
|
||||
show_activity: boolean;
|
||||
|
||||
@Column()
|
||||
type: string;
|
||||
|
||||
@Column()
|
||||
verified: boolean;
|
||||
|
||||
@Column({ select: false })
|
||||
visibility: number;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { User } from ".";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { Role } from "./Role";
|
||||
|
||||
@Entity("emojis")
|
||||
export class Emoji extends BaseClass {
|
||||
@Column()
|
||||
animated: boolean;
|
||||
|
||||
@Column()
|
||||
available: boolean; // whether this emoji can be used, may be false due to various reasons
|
||||
|
||||
@Column()
|
||||
guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((emoji: Emoji) => emoji.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User)
|
||||
user: User;
|
||||
|
||||
@Column()
|
||||
managed: boolean;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
require_colons: boolean;
|
||||
|
||||
@Column({ type: "simple-array" })
|
||||
roles: string[]; // roles this emoji is whitelisted to (new discord feature?)
|
||||
|
||||
@Column({ type: "simple-array", nullable: true })
|
||||
groups: string[]; // user groups this emoji is whitelisted to (Fosscord extension)
|
||||
}
|
||||
@@ -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 "..";
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
|
||||
import { BaseClass } from "./BaseClass";
|
||||
|
||||
@Entity("groups")
|
||||
export class UserGroup extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
parent?: BigInt;
|
||||
|
||||
@Column()
|
||||
color: number;
|
||||
|
||||
@Column()
|
||||
hoist: boolean;
|
||||
|
||||
@Column()
|
||||
mentionable: boolean;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
rights: BigInt;
|
||||
|
||||
@Column()
|
||||
position: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
icon: BigInt;
|
||||
|
||||
@Column({ nullable: true })
|
||||
unicode_emoji: BigInt;
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
|
||||
import { OrmUtils } from "../util/imports/OrmUtils";
|
||||
import { Config, handleFile, Snowflake } from "..";
|
||||
import { Ban } from "./Ban";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
import { Emoji } from "./Emoji";
|
||||
import { Invite } from "./Invite";
|
||||
import { Member } from "./Member";
|
||||
import { Role } from "./Role";
|
||||
import { Sticker } from "./Sticker";
|
||||
import { Template } from "./Template";
|
||||
import { User } from "./User";
|
||||
import { VoiceState } from "./VoiceState";
|
||||
import { Webhook } from "./Webhook";
|
||||
|
||||
// TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0}
|
||||
// TODO: guild_scheduled_events
|
||||
// TODO: stage_instances
|
||||
// TODO: threads
|
||||
// TODO:
|
||||
// "keywords": [
|
||||
// "Genshin Impact",
|
||||
// "Paimon",
|
||||
// "Honkai Impact",
|
||||
// "ARPG",
|
||||
// "Open-World",
|
||||
// "Waifu",
|
||||
// "Anime",
|
||||
// "Genshin",
|
||||
// "miHoYo",
|
||||
// "Gacha"
|
||||
// ],
|
||||
|
||||
export const PublicGuildRelations = [
|
||||
"channels",
|
||||
"emojis",
|
||||
"members",
|
||||
"roles",
|
||||
"stickers",
|
||||
"voice_states",
|
||||
"members.user",
|
||||
];
|
||||
|
||||
@Entity("guilds")
|
||||
export class Guild extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
@RelationId((guild: Guild) => guild.afk_channel)
|
||||
afk_channel_id?: string;
|
||||
|
||||
@JoinColumn({ name: "afk_channel_id" })
|
||||
@ManyToOne(() => Channel)
|
||||
afk_channel?: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
afk_timeout?: number = Config.get().defaults.guild.afkTimeout;
|
||||
|
||||
// * commented out -> use owner instead
|
||||
// application id of the guild creator if it is bot-created
|
||||
// @Column({ nullable: true })
|
||||
// application?: string;
|
||||
|
||||
@JoinColumn({ name: "ban_ids" })
|
||||
@OneToMany(() => Ban, (ban: Ban) => ban.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
bans: Ban[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
banner?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
default_message_notifications?: number = Config.get().defaults.guild.defaultMessageNotifications;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
discovery_splash?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
explicit_content_filter?: number = Config.get().defaults.guild.explicitContentFilter;
|
||||
|
||||
@Column({ type: "simple-array" })
|
||||
features: string[]; //TODO use enum
|
||||
//TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features
|
||||
|
||||
@Column({ nullable: true })
|
||||
primary_category_id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
large?: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
max_members?: number = Config.get().limits.guild.maxMembers; // e.g. default 100.000
|
||||
|
||||
@Column({ nullable: true })
|
||||
max_presences?: number = Config.get().defaults.guild.maxPresences;
|
||||
|
||||
@Column({ nullable: true })
|
||||
max_video_channel_users?: number = Config.get().defaults.guild.maxVideoChannelUsers; // ? default: 25, is this max 25 streaming or watching
|
||||
|
||||
@Column({ nullable: true })
|
||||
member_count?: number = 0;
|
||||
|
||||
@Column({ nullable: true })
|
||||
presence_count?: number = 0; // users online
|
||||
|
||||
@OneToMany(() => Member, (member: Member) => member.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
members: Member[];
|
||||
|
||||
@JoinColumn({ name: "role_ids" })
|
||||
@OneToMany(() => Role, (role: Role) => role.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
roles: Role[];
|
||||
|
||||
@JoinColumn({ name: "channel_ids" })
|
||||
@OneToMany(() => Channel, (channel: Channel) => channel.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
channels: Channel[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((guild: Guild) => guild.template)
|
||||
template_id?: string;
|
||||
|
||||
@JoinColumn({ name: "template_id", referencedColumnName: "id" })
|
||||
@ManyToOne(() => Template)
|
||||
template: Template;
|
||||
|
||||
@JoinColumn({ name: "emoji_ids" })
|
||||
@OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
emojis: Emoji[];
|
||||
|
||||
@JoinColumn({ name: "sticker_ids" })
|
||||
@OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
stickers: Sticker[];
|
||||
|
||||
@JoinColumn({ name: "invite_ids" })
|
||||
@OneToMany(() => Invite, (invite: Invite) => invite.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
invites: Invite[];
|
||||
|
||||
@JoinColumn({ name: "voice_state_ids" })
|
||||
@OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
voice_states: VoiceState[];
|
||||
|
||||
@JoinColumn({ name: "webhook_ids" })
|
||||
@OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
webhooks: Webhook[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
mfa_level?: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((guild: Guild) => guild.owner)
|
||||
owner_id?: string; // optional to allow for ownerless guilds
|
||||
|
||||
@JoinColumn({ name: "owner_id", referencedColumnName: "id" })
|
||||
@ManyToOne(() => User)
|
||||
owner?: User; // optional to allow for ownerless guilds
|
||||
|
||||
@Column({ nullable: true })
|
||||
preferred_locale?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
premium_subscription_count?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
premium_tier?: number; // crowd premium level
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((guild: Guild) => guild.public_updates_channel)
|
||||
public_updates_channel_id: string;
|
||||
|
||||
@JoinColumn({ name: "public_updates_channel_id" })
|
||||
@ManyToOne(() => Channel)
|
||||
public_updates_channel?: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((guild: Guild) => guild.rules_channel)
|
||||
rules_channel_id?: string;
|
||||
|
||||
@JoinColumn({ name: "rules_channel_id" })
|
||||
@ManyToOne(() => Channel)
|
||||
rules_channel?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
region?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
splash?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((guild: Guild) => guild.system_channel)
|
||||
system_channel_id?: string;
|
||||
|
||||
@JoinColumn({ name: "system_channel_id" })
|
||||
@ManyToOne(() => Channel)
|
||||
system_channel?: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
system_channel_flags?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
unavailable?: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
verification_level?: number;
|
||||
|
||||
@Column({ type: "simple-json" })
|
||||
welcome_screen: {
|
||||
enabled: boolean;
|
||||
description: string;
|
||||
welcome_channels: {
|
||||
description: string;
|
||||
emoji_id?: string;
|
||||
emoji_name: string;
|
||||
channel_id: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((guild: Guild) => guild.widget_channel)
|
||||
widget_channel_id?: string;
|
||||
|
||||
@JoinColumn({ name: "widget_channel_id" })
|
||||
@ManyToOne(() => Channel)
|
||||
widget_channel?: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
widget_enabled?: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
nsfw_level?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
nsfw?: boolean;
|
||||
|
||||
// TODO: nested guilds
|
||||
@Column({ nullable: true })
|
||||
parent?: string;
|
||||
|
||||
// only for developer portal
|
||||
permissions?: number;
|
||||
|
||||
//new guild settings, 11/08/2022:
|
||||
@Column({ nullable: true })
|
||||
premium_progress_bar_enabled: boolean = false;
|
||||
|
||||
static async createGuild(body: {
|
||||
name?: string;
|
||||
icon?: string | null;
|
||||
owner_id?: string;
|
||||
channels?: Partial<Channel>[];
|
||||
}) {
|
||||
const guild_id = Snowflake.generate();
|
||||
|
||||
const guild: Guild = OrmUtils.mergeDeep(new Guild(), {
|
||||
name: body.name || "Fosscord",
|
||||
icon: await handleFile(`/icons/${guild_id}`, body.icon as string),
|
||||
region: Config.get().regions.default,
|
||||
owner_id: body.owner_id, // TODO: need to figure out a way for ownerless guilds and multiply-owned guilds
|
||||
afk_timeout: 300,
|
||||
default_message_notifications: 1, // defaults effect: setting the push default at mentions-only will save a lot
|
||||
explicit_content_filter: 0,
|
||||
features: [],
|
||||
primary_category_id: null,
|
||||
id: guild_id,
|
||||
max_members: 250000,
|
||||
max_presences: 250000,
|
||||
max_video_channel_users: 200,
|
||||
presence_count: 0,
|
||||
member_count: 0, // will automatically be increased by addMember()
|
||||
mfa_level: 0,
|
||||
preferred_locale: "en-US",
|
||||
premium_subscription_count: 0,
|
||||
premium_tier: 0,
|
||||
system_channel_flags: 4, // defaults effect: suppress the setup tips to save performance
|
||||
unavailable: false,
|
||||
nsfw: false,
|
||||
nsfw_level: 0,
|
||||
verification_level: 0,
|
||||
welcome_screen: {
|
||||
enabled: false,
|
||||
description: "Fill in your description",
|
||||
welcome_channels: [],
|
||||
},
|
||||
widget_enabled: true, // NB: don't set it as false to prevent artificial restrictions
|
||||
});
|
||||
await guild.save();
|
||||
|
||||
// we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error
|
||||
// TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage
|
||||
let role: Role = OrmUtils.mergeDeep(new Role(), {
|
||||
id: guild_id,
|
||||
guild_id: guild_id,
|
||||
color: 0,
|
||||
hoist: false,
|
||||
managed: false,
|
||||
// NB: in Fosscord, every role will be non-managed, as we use user-groups instead of roles for managed groups
|
||||
mentionable: false,
|
||||
name: "@everyone",
|
||||
permissions: String("2251804225"),
|
||||
position: 0,
|
||||
icon: null,
|
||||
unicode_emoji: null,
|
||||
});
|
||||
await role.save();
|
||||
|
||||
if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }];
|
||||
|
||||
const ids = new Map();
|
||||
|
||||
body.channels.forEach((x) => {
|
||||
if (x.id) {
|
||||
ids.set(x.id, Snowflake.generate());
|
||||
}
|
||||
});
|
||||
|
||||
for (const channel of body.channels?.sort((a, b) => (a.parent_id ? 1 : -1))) {
|
||||
let id = ids.get(channel.id) || Snowflake.generate();
|
||||
|
||||
let parent_id = ids.get(channel.parent_id);
|
||||
|
||||
await Channel.createChannel({ ...channel, guild_id, id, parent_id }, body.owner_id, {
|
||||
keepId: true,
|
||||
skipExistsCheck: true,
|
||||
skipPermissionCheck: true,
|
||||
skipEventEmit: true,
|
||||
});
|
||||
}
|
||||
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId, PrimaryColumn } from "typeorm";
|
||||
import { Member } from "./Member";
|
||||
import { BaseClassWithoutId } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
import { Guild } from "./Guild";
|
||||
import { User } from "./User";
|
||||
import { random } from "@fosscord/api";
|
||||
|
||||
export const PublicInviteRelation = ["inviter", "guild", "channel"];
|
||||
|
||||
@Entity("invites")
|
||||
export class Invite extends BaseClassWithoutId {
|
||||
@PrimaryColumn()
|
||||
code: string = random();
|
||||
|
||||
@Column()
|
||||
temporary: boolean = true;
|
||||
|
||||
@Column()
|
||||
uses: number = 0;
|
||||
|
||||
@Column()
|
||||
max_uses: number;
|
||||
|
||||
@Column()
|
||||
max_age: number;
|
||||
|
||||
@Column()
|
||||
created_at: Date = new Date();
|
||||
|
||||
@Column()
|
||||
expires_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((invite: Invite) => invite.guild)
|
||||
guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((invite: Invite) => invite.channel)
|
||||
channel_id: string;
|
||||
|
||||
@JoinColumn({ name: "channel_id" })
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
channel: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((invite: Invite) => invite.inviter)
|
||||
inviter_id: string;
|
||||
|
||||
@JoinColumn({ name: "inviter_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE"
|
||||
})
|
||||
inviter: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((invite: Invite) => invite.target_user)
|
||||
target_user_id: string;
|
||||
|
||||
@JoinColumn({ name: "target_user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62
|
||||
|
||||
@Column({ nullable: true })
|
||||
target_user_type?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
vanity_url?: boolean;
|
||||
|
||||
static async joinGuild(user_id: string, code: string) {
|
||||
const invite = await Invite.findOneOrFail({ where: { code } });
|
||||
if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code });
|
||||
else await invite.save();
|
||||
|
||||
await Member.addToGuild(user_id, invite.guild_id);
|
||||
return invite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
import { PublicUser, User } from "./User";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
RelationId,
|
||||
} from "typeorm";
|
||||
import { Guild } from "./Guild";
|
||||
import { Config, emitEvent } from "../util";
|
||||
import {
|
||||
GuildCreateEvent,
|
||||
GuildDeleteEvent,
|
||||
GuildMemberAddEvent,
|
||||
GuildMemberRemoveEvent,
|
||||
GuildMemberUpdateEvent,
|
||||
} from "../interfaces";
|
||||
import { HTTPError } from "../util/imports/HTTPError";
|
||||
import { Role } from "./Role";
|
||||
import { BaseClassWithoutId } from "./BaseClass";
|
||||
import { Ban, PublicGuildRelations } from ".";
|
||||
import { DiscordApiErrors } from "../util/Constants";
|
||||
import { OrmUtils } from "../util/imports/OrmUtils";
|
||||
|
||||
export const MemberPrivateProjection: (keyof Member)[] = [
|
||||
"id",
|
||||
"guild",
|
||||
"guild_id",
|
||||
"deaf",
|
||||
"joined_at",
|
||||
"last_message_id",
|
||||
"mute",
|
||||
"nick",
|
||||
"pending",
|
||||
"premium_since",
|
||||
"roles",
|
||||
"settings",
|
||||
"user",
|
||||
];
|
||||
|
||||
@Entity("members")
|
||||
@Index(["id", "guild_id"], { unique: true })
|
||||
export class Member extends BaseClassWithoutId {
|
||||
@PrimaryGeneratedColumn()
|
||||
index: string;
|
||||
|
||||
@Column()
|
||||
@RelationId((member: Member) => member.user)
|
||||
id: string;
|
||||
|
||||
@JoinColumn({ name: "id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
|
||||
@Column()
|
||||
@RelationId((member: Member) => member.guild)
|
||||
guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
nick?: string;
|
||||
|
||||
@JoinTable({
|
||||
name: "member_roles",
|
||||
joinColumn: { name: "index", referencedColumnName: "index" },
|
||||
inverseJoinColumn: {
|
||||
name: "role_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
@ManyToMany(() => Role, { cascade: true })
|
||||
roles: Role[];
|
||||
|
||||
@Column()
|
||||
joined_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
premium_since?: Date;
|
||||
|
||||
@Column()
|
||||
deaf: boolean;
|
||||
|
||||
@Column()
|
||||
mute: boolean;
|
||||
|
||||
@Column()
|
||||
pending: boolean;
|
||||
|
||||
@Column({ type: "simple-json", select: false })
|
||||
settings: UserGuildSettings;
|
||||
|
||||
@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: add this when we have proper read receipts
|
||||
// @Column({ type: "simple-json" })
|
||||
// read_state: ReadState;
|
||||
|
||||
static async IsInGuildOrFail(user_id: string, guild_id: string) {
|
||||
if (await Member.count({ where: { id: user_id, guild: { id: guild_id } } })) return true;
|
||||
throw new HTTPError("You are not member of this guild", 403);
|
||||
}
|
||||
|
||||
static async removeFromGuild(user_id: string, guild_id: string) {
|
||||
const guild = await Guild.findOneOrFail({ select: ["owner_id", "member_count"], where: { id: guild_id } });
|
||||
if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild");
|
||||
const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] });
|
||||
|
||||
// use promise all to execute all promises at the same time -> save time
|
||||
//TODO: check for bugs
|
||||
if (guild.member_count) guild.member_count--;
|
||||
return Promise.all([
|
||||
Member.delete({
|
||||
id: user_id,
|
||||
guild_id,
|
||||
}),
|
||||
//Guild.decrement({ id: guild_id }, "member_count", -1),
|
||||
|
||||
emitEvent({
|
||||
event: "GUILD_DELETE",
|
||||
data: {
|
||||
id: guild_id,
|
||||
},
|
||||
user_id: user_id,
|
||||
} as GuildDeleteEvent),
|
||||
emitEvent({
|
||||
event: "GUILD_MEMBER_REMOVE",
|
||||
data: { guild_id, user: member.user },
|
||||
guild_id,
|
||||
} as GuildMemberRemoveEvent),
|
||||
]);
|
||||
}
|
||||
|
||||
static async addRole(user_id: string, guild_id: string, role_id: string) {
|
||||
const [member, role] = await Promise.all([
|
||||
// @ts-ignore
|
||||
Member.findOneOrFail({
|
||||
where: { id: user_id, guild_id },
|
||||
relations: ["user", "roles"], // we don't want to load the role objects just the ids
|
||||
select: ["index"],
|
||||
}),
|
||||
Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }),
|
||||
]);
|
||||
member.roles.push(OrmUtils.mergeDeep(new Role(), { id: role_id }));
|
||||
|
||||
await Promise.all([
|
||||
member.save(),
|
||||
emitEvent({
|
||||
event: "GUILD_MEMBER_UPDATE",
|
||||
data: {
|
||||
guild_id,
|
||||
user: member.user,
|
||||
roles: member.roles.map((x) => x.id),
|
||||
},
|
||||
guild_id,
|
||||
} as GuildMemberUpdateEvent),
|
||||
]);
|
||||
}
|
||||
|
||||
static async removeRole(user_id: string, guild_id: string, role_id: string) {
|
||||
const [member] = await Promise.all([
|
||||
// @ts-ignore
|
||||
Member.findOneOrFail({
|
||||
where: { id: user_id, guild_id },
|
||||
relations: ["user", "roles"], // we don't want to load the role objects just the ids
|
||||
select: ["index"],
|
||||
}),
|
||||
await Role.findOneOrFail({ where: { id: role_id, guild_id } }),
|
||||
]);
|
||||
member.roles = member.roles.filter((x) => x.id == role_id);
|
||||
|
||||
await Promise.all([
|
||||
member.save(),
|
||||
emitEvent({
|
||||
event: "GUILD_MEMBER_UPDATE",
|
||||
data: {
|
||||
guild_id,
|
||||
user: member.user,
|
||||
roles: member.roles.map((x) => x.id),
|
||||
},
|
||||
guild_id,
|
||||
} as GuildMemberUpdateEvent),
|
||||
]);
|
||||
}
|
||||
|
||||
static async changeNickname(user_id: string, guild_id: string, nickname: string) {
|
||||
const member = await Member.findOneOrFail({
|
||||
where: {
|
||||
id: user_id,
|
||||
guild_id,
|
||||
},
|
||||
relations: ["user"],
|
||||
});
|
||||
member.nick = nickname;
|
||||
|
||||
await Promise.all([
|
||||
member.save(),
|
||||
|
||||
emitEvent({
|
||||
event: "GUILD_MEMBER_UPDATE",
|
||||
data: {
|
||||
guild_id,
|
||||
user: member.user,
|
||||
nick: nickname,
|
||||
},
|
||||
guild_id,
|
||||
} as GuildMemberUpdateEvent),
|
||||
]);
|
||||
}
|
||||
|
||||
static async addToGuild(user_id: string, guild_id: string) {
|
||||
const user = await User.getPublicUser(user_id);
|
||||
const isBanned = await Ban.count({ where: { guild_id, user_id } });
|
||||
if (isBanned) {
|
||||
throw DiscordApiErrors.USER_BANNED;
|
||||
}
|
||||
const { maxGuilds } = Config.get().limits.user;
|
||||
const guild_count = await Member.count({ where: { id: user_id } });
|
||||
if (guild_count >= maxGuilds) {
|
||||
throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
|
||||
}
|
||||
|
||||
const guild = await Guild.findOneOrFail({
|
||||
where: {
|
||||
id: guild_id,
|
||||
},
|
||||
relations: PublicGuildRelations,
|
||||
});
|
||||
|
||||
if (await Member.count({ where: { id: user.id, guild: { id: guild_id } } }))
|
||||
throw new HTTPError("You are already a member of this guild", 400);
|
||||
|
||||
const member = {
|
||||
id: user_id,
|
||||
guild_id,
|
||||
nick: undefined,
|
||||
roles: [guild_id], // @everyone role
|
||||
joined_at: new Date(),
|
||||
premium_since: null,
|
||||
deaf: false,
|
||||
mute: false,
|
||||
pending: false,
|
||||
};
|
||||
//TODO: check for bugs
|
||||
if (guild.member_count) guild.member_count++;
|
||||
await Promise.all([
|
||||
OrmUtils.mergeDeep(new Member(), {
|
||||
...member,
|
||||
roles: [OrmUtils.mergeDeep(new Role(), { id: guild_id })],
|
||||
// read_state: {},
|
||||
settings: {
|
||||
channel_overrides: [],
|
||||
message_notifications: 0,
|
||||
mobile_push: true,
|
||||
muted: false,
|
||||
suppress_everyone: false,
|
||||
suppress_roles: false,
|
||||
version: 0,
|
||||
},
|
||||
// Member.save is needed because else the roles relations wouldn't be updated
|
||||
}).save(),
|
||||
//Guild.increment({ id: guild_id }, "member_count", 1),
|
||||
emitEvent({
|
||||
event: "GUILD_MEMBER_ADD",
|
||||
data: {
|
||||
...member,
|
||||
user,
|
||||
guild_id,
|
||||
},
|
||||
guild_id,
|
||||
} as GuildMemberAddEvent),
|
||||
emitEvent({
|
||||
event: "GUILD_CREATE",
|
||||
data: {
|
||||
...guild,
|
||||
members: [...guild.members, { ...member, user }],
|
||||
member_count: (guild.member_count || 0) + 1,
|
||||
guild_hashes: {},
|
||||
guild_scheduled_events: [],
|
||||
joined_at: member.joined_at,
|
||||
presences: [],
|
||||
stage_instances: [],
|
||||
threads: [],
|
||||
},
|
||||
user_id,
|
||||
} as GuildCreateEvent),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserGuildSettings {
|
||||
channel_overrides: {
|
||||
channel_id: string;
|
||||
message_notifications: number;
|
||||
mute_config: MuteConfig;
|
||||
muted: boolean;
|
||||
}[];
|
||||
message_notifications: number;
|
||||
mobile_push: boolean;
|
||||
mute_config: MuteConfig;
|
||||
muted: boolean;
|
||||
suppress_everyone: boolean;
|
||||
suppress_roles: boolean;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface MuteConfig {
|
||||
end_time: number;
|
||||
selected_time_window: number;
|
||||
}
|
||||
|
||||
export type PublicMemberKeys =
|
||||
| "id"
|
||||
| "guild_id"
|
||||
| "nick"
|
||||
| "roles"
|
||||
| "joined_at"
|
||||
| "pending"
|
||||
| "deaf"
|
||||
| "mute"
|
||||
| "premium_since";
|
||||
|
||||
export const PublicMemberProjection: PublicMemberKeys[] = [
|
||||
"id",
|
||||
"guild_id",
|
||||
"nick",
|
||||
"roles",
|
||||
"joined_at",
|
||||
"pending",
|
||||
"deaf",
|
||||
"mute",
|
||||
"premium_since",
|
||||
];
|
||||
|
||||
// @ts-ignore
|
||||
export type PublicMember = Pick<Member, Omit<PublicMemberKeys, "roles">> & {
|
||||
user: PublicUser;
|
||||
roles: string[]; // only role ids not objects
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
import { User } from "./User";
|
||||
import { Member } from "./Member";
|
||||
import { Role } from "./Role";
|
||||
import { Channel } from "./Channel";
|
||||
import { InteractionType } from "../interfaces/Interaction";
|
||||
import { Application } from "./Application";
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
RelationId,
|
||||
RemoveOptions,
|
||||
UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { Webhook } from "./Webhook";
|
||||
import { Sticker } from "./Sticker";
|
||||
import { Attachment } from "./Attachment";
|
||||
|
||||
export enum MessageType {
|
||||
DEFAULT = 0,
|
||||
RECIPIENT_ADD = 1,
|
||||
RECIPIENT_REMOVE = 2,
|
||||
CALL = 3,
|
||||
CHANNEL_NAME_CHANGE = 4,
|
||||
CHANNEL_ICON_CHANGE = 5,
|
||||
CHANNEL_PINNED_MESSAGE = 6,
|
||||
GUILD_MEMBER_JOIN = 7,
|
||||
USER_PREMIUM_GUILD_SUBSCRIPTION = 8,
|
||||
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9,
|
||||
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10,
|
||||
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11,
|
||||
CHANNEL_FOLLOW_ADD = 12,
|
||||
ACTION = 13, // /me messages
|
||||
GUILD_DISCOVERY_DISQUALIFIED = 14,
|
||||
GUILD_DISCOVERY_REQUALIFIED = 15,
|
||||
ENCRYPTED = 16,
|
||||
REPLY = 19,
|
||||
APPLICATION_COMMAND = 20, // application command or self command invocation
|
||||
ROUTE_ADDED = 41, // custom message routing: new route affecting that channel
|
||||
ROUTE_DISABLED = 42, // custom message routing: given route no longer affecting that channel
|
||||
SELF_COMMAND_SCRIPT = 43, // self command scripts
|
||||
ENCRYPTION = 50,
|
||||
CUSTOM_START = 63,
|
||||
UNHANDLED = 255
|
||||
}
|
||||
|
||||
@Entity("messages")
|
||||
@Index(["channel_id", "id"], { unique: true })
|
||||
export class Message extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
@RelationId((message: Message) => message.channel)
|
||||
@Index()
|
||||
channel_id: string;
|
||||
|
||||
@JoinColumn({ name: "channel_id" })
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
channel: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((message: Message) => message.guild)
|
||||
guild_id?: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild?: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((message: Message) => message.author)
|
||||
@Index()
|
||||
author_id: string;
|
||||
|
||||
@JoinColumn({ name: "author_id", referencedColumnName: "id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
author?: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((message: Message) => message.member)
|
||||
member_id: string;
|
||||
|
||||
@JoinColumn({ name: "member_id", referencedColumnName: "id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
member?: Member;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((message: Message) => message.webhook)
|
||||
webhook_id: string;
|
||||
|
||||
@JoinColumn({ name: "webhook_id" })
|
||||
@ManyToOne(() => Webhook)
|
||||
webhook?: Webhook;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((message: Message) => message.application)
|
||||
application_id: string;
|
||||
|
||||
@JoinColumn({ name: "application_id" })
|
||||
@ManyToOne(() => Application)
|
||||
application?: Application;
|
||||
|
||||
@Column({ nullable: true })
|
||||
content?: string;
|
||||
|
||||
@Column()
|
||||
@CreateDateColumn()
|
||||
timestamp: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
edited_timestamp?: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
tts?: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
mention_everyone?: boolean;
|
||||
|
||||
@JoinTable({ name: "message_user_mentions" })
|
||||
@ManyToMany(() => User)
|
||||
mentions: User[];
|
||||
|
||||
@JoinTable({ name: "message_role_mentions" })
|
||||
@ManyToMany(() => Role)
|
||||
mention_roles: Role[];
|
||||
|
||||
@JoinTable({ name: "message_channel_mentions" })
|
||||
@ManyToMany(() => Channel)
|
||||
mention_channels: Channel[];
|
||||
|
||||
@JoinTable({ name: "message_stickers" })
|
||||
@ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" })
|
||||
sticker_items?: Sticker[];
|
||||
|
||||
@OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
attachments?: Attachment[];
|
||||
|
||||
@Column({ type: "simple-json" })
|
||||
embeds: Embed[];
|
||||
|
||||
@Column({ type: "simple-json" })
|
||||
reactions: Reaction[];
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
nonce?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
pinned?: boolean;
|
||||
|
||||
@Column({ type: "int" })
|
||||
type: MessageType;
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
activity?: {
|
||||
type: number;
|
||||
party_id: string;
|
||||
};
|
||||
|
||||
@Column({ nullable: true })
|
||||
flags?: string;
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
message_reference?: {
|
||||
message_id: string;
|
||||
channel_id?: string;
|
||||
guild_id?: string;
|
||||
};
|
||||
|
||||
@JoinColumn({ name: "message_reference_id" })
|
||||
@ManyToOne(() => Message)
|
||||
referenced_message?: Message;
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
interaction?: {
|
||||
id: string;
|
||||
type: InteractionType;
|
||||
name: string;
|
||||
user_id: string; // the user who invoked the interaction
|
||||
// user: User; // TODO: autopopulate user
|
||||
};
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
components?: MessageComponent[];
|
||||
}
|
||||
|
||||
export interface MessageComponent {
|
||||
type: number;
|
||||
style?: number;
|
||||
label?: string;
|
||||
emoji?: PartialEmoji;
|
||||
custom_id?: string;
|
||||
url?: string;
|
||||
disabled?: boolean;
|
||||
components: MessageComponent[];
|
||||
}
|
||||
|
||||
export enum MessageComponentType {
|
||||
Script = 0, // self command script
|
||||
ActionRow = 1,
|
||||
Button = 2,
|
||||
}
|
||||
|
||||
export interface Embed {
|
||||
title?: string; //title of embed
|
||||
type?: EmbedType; // type of embed (always "rich" for webhook embeds)
|
||||
description?: string; // description of embed
|
||||
url?: string; // url of embed
|
||||
timestamp?: Date; // timestamp of embed content
|
||||
color?: number; // color code of the embed
|
||||
footer?: {
|
||||
text: string;
|
||||
icon_url?: string;
|
||||
proxy_icon_url?: string;
|
||||
}; // footer object footer information
|
||||
image?: EmbedImage; // image object image information
|
||||
thumbnail?: EmbedImage; // thumbnail object thumbnail information
|
||||
video?: EmbedImage; // video object video information
|
||||
provider?: {
|
||||
name?: string;
|
||||
url?: string;
|
||||
}; // provider object provider information
|
||||
author?: {
|
||||
name?: string;
|
||||
url?: string;
|
||||
icon_url?: string;
|
||||
proxy_icon_url?: string;
|
||||
}; // author object author information
|
||||
fields?: {
|
||||
name: string;
|
||||
value: string;
|
||||
inline?: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export enum EmbedType {
|
||||
rich = "rich",
|
||||
image = "image",
|
||||
video = "video",
|
||||
gifv = "gifv",
|
||||
article = "article",
|
||||
link = "link",
|
||||
}
|
||||
|
||||
export interface EmbedImage {
|
||||
url?: string;
|
||||
proxy_url?: string;
|
||||
height?: number;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export interface Reaction {
|
||||
count: number;
|
||||
//// not saved in the database // me: boolean; // whether the current user reacted using this emoji
|
||||
emoji: PartialEmoji;
|
||||
user_ids: string[];
|
||||
}
|
||||
|
||||
export interface PartialEmoji {
|
||||
id?: string;
|
||||
name: string;
|
||||
animated?: boolean;
|
||||
}
|
||||
|
||||
export interface AllowedMentions {
|
||||
parse?: ("users" | "roles" | "everyone")[];
|
||||
roles?: string[];
|
||||
users?: string[];
|
||||
replied_user?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { BaseClassWithoutId } from ".";
|
||||
|
||||
export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb")
|
||||
? ObjectIdColumn
|
||||
: PrimaryGeneratedColumn;
|
||||
|
||||
@Entity("migrations")
|
||||
export class Migration extends BaseClassWithoutId {
|
||||
@PrimaryIdAutoGenerated()
|
||||
id: number;
|
||||
|
||||
@Column({ type: "bigint" })
|
||||
timestamp: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, Unique } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity("notes")
|
||||
@Unique(["owner", "target"])
|
||||
export class Note extends BaseClass {
|
||||
@JoinColumn({ name: "owner_id" })
|
||||
@ManyToOne(() => User, { onDelete: "CASCADE" })
|
||||
owner: User;
|
||||
|
||||
@JoinColumn({ name: "target_id" })
|
||||
@ManyToOne(() => User, { onDelete: "CASCADE" })
|
||||
target: User;
|
||||
|
||||
@Column()
|
||||
content: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Column, Entity } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
|
||||
@Entity("rate_limits")
|
||||
export class RateLimit extends BaseClass {
|
||||
@Column() // no relation as it also
|
||||
executor_id: string;
|
||||
|
||||
@Column()
|
||||
hits: number;
|
||||
|
||||
@Column()
|
||||
blocked: boolean;
|
||||
|
||||
@Column()
|
||||
expires_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
import { Message } from "./Message";
|
||||
import { User } from "./User";
|
||||
|
||||
// for read receipts
|
||||
// notification cursor and public read receipt need to be forwards-only (the former to prevent re-pinging when marked as unread, and the latter to be acceptable as a legal acknowledgement in criminal proceedings), and private read marker needs to be advance-rewind capable
|
||||
// public read receipt ≥ notification cursor ≥ private fully read marker
|
||||
|
||||
@Entity("read_states")
|
||||
@Index(["channel_id", "user_id"], { unique: true })
|
||||
export class ReadState extends BaseClass {
|
||||
@Column()
|
||||
@RelationId((read_state: ReadState) => read_state.channel)
|
||||
channel_id: string;
|
||||
|
||||
@JoinColumn({ name: "channel_id" })
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
channel: Channel;
|
||||
|
||||
@Column()
|
||||
@RelationId((read_state: ReadState) => read_state.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
|
||||
// fully read marker
|
||||
@Column({ nullable: true })
|
||||
last_message_id: string;
|
||||
|
||||
// public read receipt
|
||||
@Column({ nullable: true })
|
||||
public_ack: string;
|
||||
|
||||
// notification cursor / private read receipt
|
||||
@Column({ nullable: true })
|
||||
notifications_cursor: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
last_pin_timestamp?: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
mention_count: number;
|
||||
|
||||
// @Column({ nullable: true })
|
||||
// TODO: derive this from (last_message_id=notifications_cursor=public_ack)=true
|
||||
manual: boolean;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
|
||||
@Entity("recipients")
|
||||
export class Recipient extends BaseClass {
|
||||
@Column()
|
||||
@RelationId((recipient: Recipient) => recipient.channel)
|
||||
channel_id: string;
|
||||
|
||||
@JoinColumn({ name: "channel_id" })
|
||||
@ManyToOne(() => require("./Channel").Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
channel: import("./Channel").Channel;
|
||||
|
||||
@Column()
|
||||
@RelationId((recipient: Recipient) => recipient.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => require("./User").User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: import("./User").User;
|
||||
|
||||
@Column({ default: false })
|
||||
closed: boolean;
|
||||
|
||||
// TODO: settings/mute/nick/added at/encryption keys/read_state
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { User } from "./User";
|
||||
|
||||
export enum RelationshipType {
|
||||
outgoing = 4,
|
||||
incoming = 3,
|
||||
blocked = 2,
|
||||
friends = 1,
|
||||
}
|
||||
|
||||
@Entity("relationships")
|
||||
@Index(["from_id", "to_id"], { unique: true })
|
||||
export class Relationship extends BaseClass {
|
||||
@Column({})
|
||||
@RelationId((relationship: Relationship) => relationship.from)
|
||||
from_id: string;
|
||||
|
||||
@JoinColumn({ name: "from_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
from: User;
|
||||
|
||||
@Column({})
|
||||
@RelationId((relationship: Relationship) => relationship.to)
|
||||
to_id: string;
|
||||
|
||||
@JoinColumn({ name: "to_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
to: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
nickname?: string;
|
||||
|
||||
@Column({ type: "int" })
|
||||
type: RelationshipType;
|
||||
|
||||
toPublicRelationship() {
|
||||
return {
|
||||
id: this.to?.id || this.to_id,
|
||||
type: this.type,
|
||||
nickname: this.nickname,
|
||||
user: this.to?.toPublicUser(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
|
||||
@Entity("roles")
|
||||
export class Role extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
@RelationId((role: Role) => role.guild)
|
||||
guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild: Guild;
|
||||
|
||||
@Column()
|
||||
color: number;
|
||||
|
||||
@Column()
|
||||
hoist: boolean;
|
||||
|
||||
@Column()
|
||||
managed: boolean;
|
||||
|
||||
@Column()
|
||||
mentionable: boolean;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
permissions: string;
|
||||
|
||||
@Column()
|
||||
position: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
icon: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
unicode_emoji: string;
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
tags?: {
|
||||
bot_id?: string;
|
||||
integration_id?: string;
|
||||
premium_subscriber?: boolean;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { User } from "./User";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { Status } from "../interfaces/Status";
|
||||
import { Activity } from "../interfaces/Activity";
|
||||
|
||||
//TODO we need to remove all sessions on server start because if the server crashes without closing websockets it won't delete them
|
||||
|
||||
@Entity("sessions")
|
||||
export class Session extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
@RelationId((session: Session) => session.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
|
||||
//TODO check, should be 32 char long hex string
|
||||
@Column({ nullable: false, select: false })
|
||||
session_id: string;
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
activities: Activity[];
|
||||
|
||||
// TODO client_status
|
||||
@Column({ type: "simple-json", select: false })
|
||||
client_info: {
|
||||
client: string;
|
||||
os: string;
|
||||
version: number;
|
||||
};
|
||||
|
||||
@Column({ nullable: false, type: "varchar" })
|
||||
status: Status; //TODO enum
|
||||
}
|
||||
|
||||
export const PrivateSessionProjection: (keyof Session)[] = [
|
||||
"user_id",
|
||||
"session_id",
|
||||
"activities",
|
||||
"client_info",
|
||||
"status",
|
||||
];
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { User } from "./User";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
|
||||
export enum StickerType {
|
||||
STANDARD = 1,
|
||||
GUILD = 2,
|
||||
}
|
||||
|
||||
export enum StickerFormatType {
|
||||
GIF = 0, // gif is a custom format type and not in discord spec
|
||||
PNG = 1,
|
||||
APNG = 2,
|
||||
LOTTIE = 3,
|
||||
}
|
||||
|
||||
@Entity("stickers")
|
||||
export class Sticker extends BaseClass {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
available?: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
tags?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((sticker: Sticker) => sticker.pack)
|
||||
pack_id?: string;
|
||||
|
||||
@JoinColumn({ name: "pack_id" })
|
||||
@ManyToOne(() => require("./StickerPack").StickerPack, {
|
||||
onDelete: "CASCADE",
|
||||
nullable: true,
|
||||
})
|
||||
pack: import("./StickerPack").StickerPack;
|
||||
|
||||
@Column({ nullable: true })
|
||||
guild_id?: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild?: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
user_id?: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user?: User;
|
||||
|
||||
@Column({ type: "int" })
|
||||
type: StickerType;
|
||||
|
||||
@Column({ type: "int" })
|
||||
format_type: StickerFormatType;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
|
||||
import { Sticker } from ".";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
|
||||
@Entity("sticker_packs")
|
||||
export class StickerPack extends BaseClass {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
banner_asset_id?: string;
|
||||
|
||||
@OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
stickers: Sticker[];
|
||||
|
||||
// sku_id: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((pack: StickerPack) => pack.cover_sticker)
|
||||
cover_sticker_id?: string;
|
||||
|
||||
@ManyToOne(() => Sticker, { nullable: true })
|
||||
@JoinColumn()
|
||||
cover_sticker?: Sticker;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { TeamMember } from "./TeamMember";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity("teams")
|
||||
export class Team extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@JoinColumn({ name: "member_ids" })
|
||||
@OneToMany(() => TeamMember, (member: TeamMember) => member.team, {
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
members: TeamMember[];
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((team: Team) => team.owner_user)
|
||||
owner_user_id: string;
|
||||
|
||||
@JoinColumn({ name: "owner_user_id" })
|
||||
@ManyToOne(() => User)
|
||||
owner_user: User;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { User } from "./User";
|
||||
|
||||
export enum TeamMemberState {
|
||||
INVITED = 1,
|
||||
ACCEPTED = 2,
|
||||
}
|
||||
|
||||
@Entity("team_members")
|
||||
export class TeamMember extends BaseClass {
|
||||
@Column({ type: "int" })
|
||||
membership_state: TeamMemberState;
|
||||
|
||||
@Column({ type: "simple-array" })
|
||||
permissions: string[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((member: TeamMember) => member.team)
|
||||
team_id: string;
|
||||
|
||||
@JoinColumn({ name: "team_id" })
|
||||
@ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
team: import("./Team").Team;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((member: TeamMember) => member.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity("templates")
|
||||
export class Template extends BaseClass {
|
||||
@Column({ unique: true })
|
||||
code: string;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
usage_count?: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((template: Template) => template.creator)
|
||||
creator_id: string;
|
||||
|
||||
@JoinColumn({ name: "creator_id" })
|
||||
@ManyToOne(() => User)
|
||||
creator: User;
|
||||
|
||||
@Column()
|
||||
created_at: Date;
|
||||
|
||||
@Column()
|
||||
updated_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((template: Template) => template.source_guild)
|
||||
source_guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "source_guild_id" })
|
||||
@ManyToOne(() => Guild)
|
||||
source_guild: Guild;
|
||||
|
||||
@Column({ type: "simple-json" })
|
||||
serialized_source_guild: Guild;
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
import { Column, Entity, FindOneOptions, FindOptionsSelectByString, JoinColumn, OneToMany, OneToOne } from "typeorm";
|
||||
import { OrmUtils } from "../util/imports/OrmUtils";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { BitField } from "../util/BitField";
|
||||
import { Relationship } from "./Relationship";
|
||||
import { ConnectedAccount } from "./ConnectedAccount";
|
||||
import { Config, FieldErrors, Snowflake, trimSpecial } from "..";
|
||||
import { Member, Session, UserSettings } from ".";
|
||||
|
||||
export enum PublicUserEnum {
|
||||
username,
|
||||
discriminator,
|
||||
id,
|
||||
public_flags,
|
||||
avatar,
|
||||
accent_color,
|
||||
banner,
|
||||
bio,
|
||||
bot,
|
||||
premium_since,
|
||||
}
|
||||
export type PublicUserKeys = keyof typeof PublicUserEnum;
|
||||
|
||||
export enum PrivateUserEnum {
|
||||
flags,
|
||||
mfa_enabled,
|
||||
email,
|
||||
phone,
|
||||
verified,
|
||||
nsfw_allowed,
|
||||
premium,
|
||||
premium_type,
|
||||
disabled,
|
||||
settings,
|
||||
// locale
|
||||
}
|
||||
export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys;
|
||||
|
||||
export const PublicUserProjection = Object.values(PublicUserEnum).filter(
|
||||
(x) => typeof x === "string"
|
||||
) as PublicUserKeys[];
|
||||
export const PrivateUserProjection = [
|
||||
...PublicUserProjection,
|
||||
...Object.values(PrivateUserEnum).filter((x) => typeof x === "string"),
|
||||
] as PrivateUserKeys[];
|
||||
|
||||
// Private user data that should never get sent to the client
|
||||
export type PublicUser = Pick<User, PublicUserKeys>;
|
||||
|
||||
export interface UserPublic extends Pick<User, PublicUserKeys> {}
|
||||
|
||||
export interface UserPrivate extends Pick<User, PrivateUserKeys> {
|
||||
locale: string;
|
||||
}
|
||||
|
||||
// TODO: add purchased_flags, premium_usage_flags
|
||||
|
||||
@Entity("users")
|
||||
export class User extends BaseClass {
|
||||
@Column()
|
||||
username: string; // username max length 32, min 2 (should be configurable)
|
||||
|
||||
@Column()
|
||||
discriminator: string; // opaque string: 4 digits on discord.com
|
||||
|
||||
setDiscriminator(val: string) {
|
||||
const number = Number(val);
|
||||
if (isNaN(number)) throw new Error("invalid discriminator");
|
||||
if (number <= 0 || number >= 10000) throw new Error("discriminator must be between 1 and 9999");
|
||||
this.discriminator = val.toString().padStart(4, "0");
|
||||
}
|
||||
|
||||
@Column({ nullable: true })
|
||||
avatar?: string; // hash of the user avatar
|
||||
|
||||
@Column({ nullable: true })
|
||||
accent_color?: number; // banner color of user
|
||||
|
||||
@Column({ nullable: true })
|
||||
banner?: string; // hash of the user banner
|
||||
|
||||
@Column({ nullable: true, select: false })
|
||||
phone?: string; // phone number of the user
|
||||
|
||||
@Column({ select: false })
|
||||
desktop: boolean = false; // if the user has desktop app installed
|
||||
|
||||
@Column({ select: false })
|
||||
mobile: boolean = false; // if the user has mobile app installed
|
||||
|
||||
@Column()
|
||||
premium: boolean = Config.get().defaults.user.premium; // if user bought individual premium
|
||||
|
||||
@Column()
|
||||
premium_type: number = Config.get().defaults.user.premium_type; // individual premium level
|
||||
|
||||
@Column()
|
||||
bot: boolean = false; // if user is bot
|
||||
|
||||
@Column()
|
||||
bio: string; // short description of the user (max 190 chars -> should be configurable)
|
||||
|
||||
@Column()
|
||||
system: boolean = false; // 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 = true; // if the user can do age-restricted actions (NSFW channels/guilds/commands) // TODO: depending on age
|
||||
|
||||
@Column({ select: false })
|
||||
mfa_enabled: boolean; // if multi factor authentication is enabled
|
||||
|
||||
@Column({ select: false, nullable: true })
|
||||
totp_secret?: string;
|
||||
|
||||
@Column({ nullable: true, select: false })
|
||||
totp_last_ticket?: string;
|
||||
|
||||
@Column()
|
||||
created_at: Date = new Date(); // registration date
|
||||
|
||||
@Column({ nullable: true })
|
||||
premium_since: Date = new Date(); // premium date
|
||||
|
||||
@Column({ select: false })
|
||||
verified: boolean = Config.get().defaults.user.verified; // if the user is offically verified
|
||||
|
||||
@Column()
|
||||
disabled: boolean = false; // if the account is disabled
|
||||
|
||||
@Column()
|
||||
deleted: boolean = false; // if the user was deleted
|
||||
|
||||
@Column({ nullable: true, select: false })
|
||||
email?: string; // email of the user
|
||||
|
||||
@Column()
|
||||
flags: string = "0"; // UserFlags // TODO: generate
|
||||
|
||||
@Column()
|
||||
public_flags: number = 0;
|
||||
|
||||
@Column({ type: "bigint" })
|
||||
rights: string = Config.get().register.defaultRights; // Rights
|
||||
|
||||
@OneToMany(() => Session, (session: Session) => session.user)
|
||||
sessions: Session[];
|
||||
|
||||
@JoinColumn({ name: "relationship_ids" })
|
||||
@OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
relationships: Relationship[];
|
||||
|
||||
@JoinColumn({ name: "connected_account_ids" })
|
||||
@OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
})
|
||||
connected_accounts: ConnectedAccount[];
|
||||
|
||||
@Column({ type: "simple-json", select: false })
|
||||
data: {
|
||||
valid_tokens_since: Date; // all tokens with a previous issue date are invalid
|
||||
hash?: string; // hash of the password, salt is saved in password (bcrypt)
|
||||
};
|
||||
|
||||
@Column({ type: "simple-array", select: false })
|
||||
fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts
|
||||
|
||||
|
||||
@OneToOne(()=> UserSettings, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
eager: false
|
||||
})
|
||||
@JoinColumn()
|
||||
settings: UserSettings;
|
||||
|
||||
// workaround to prevent fossord-unaware clients from deleting settings not used by them
|
||||
@Column({ type: "simple-json", select: false })
|
||||
extended_settings: string = "{}";
|
||||
|
||||
@Column({ type: "simple-json" })
|
||||
notes: { [key: string]: string } = {}; //key is ID of user
|
||||
|
||||
async save(): Promise<any> {
|
||||
if(!this.settings) this.settings = new UserSettings();
|
||||
this.settings.id = this.id;
|
||||
//await this.settings.save();
|
||||
return super.save();
|
||||
}
|
||||
|
||||
toPublicUser() {
|
||||
const user: any = {};
|
||||
PublicUserProjection.forEach((x) => {
|
||||
user[x] = this[x];
|
||||
});
|
||||
return user as PublicUser;
|
||||
}
|
||||
|
||||
static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
|
||||
return await User.findOneOrFail({
|
||||
where: { id: user_id },
|
||||
select: [...PublicUserProjection, ...((opts?.select as FindOptionsSelectByString<User>) || [])],
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
public static async generateDiscriminator(username: string): Promise<string | undefined> {
|
||||
if (Config.get().register.incrementingDiscriminators) {
|
||||
// discriminator will be incrementally generated
|
||||
|
||||
// First we need to figure out the currently highest discrimnator for the given username and then increment it
|
||||
const users = await User.find({ where: { username }, select: ["discriminator"] });
|
||||
const highestDiscriminator = Math.max(0, ...users.map((u) => Number(u.discriminator)));
|
||||
|
||||
const discriminator = highestDiscriminator + 1;
|
||||
if (discriminator >= 10000) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return discriminator.toString().padStart(4, "0");
|
||||
} else {
|
||||
// discriminator will be randomly generated
|
||||
|
||||
// randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists
|
||||
// TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database?
|
||||
for (let tries = 0; tries < 5; tries++) {
|
||||
const discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0");
|
||||
const exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] });
|
||||
if (!exists) return discriminator;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static async register({
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
date_of_birth,
|
||||
req,
|
||||
}: {
|
||||
username: string;
|
||||
password?: string;
|
||||
email?: string;
|
||||
date_of_birth?: Date; // "2000-04-03"
|
||||
req?: any;
|
||||
}) {
|
||||
// trim special uf8 control characters -> Backspace, Newline, ...
|
||||
username = trimSpecial(username);
|
||||
|
||||
const discriminator = await User.generateDiscriminator(username);
|
||||
if (!discriminator) {
|
||||
// We've failed to generate a valid and unused discriminator
|
||||
throw FieldErrors({
|
||||
username: {
|
||||
code: "USERNAME_TOO_MANY_USERS",
|
||||
message: req?.t("auth:register.USERNAME_TOO_MANY_USERS"),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: save date_of_birth
|
||||
// appearently discord doesn't save the date of birth and just calculate if nsfw is allowed
|
||||
// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
|
||||
const language = req?.language === "en" ? "en-US" : req?.language || "en-US";
|
||||
|
||||
const user = OrmUtils.mergeDeep(new User(), {
|
||||
//required:
|
||||
username: username,
|
||||
discriminator,
|
||||
id: Snowflake.generate(),
|
||||
email: email,
|
||||
data: {
|
||||
hash: password,
|
||||
valid_tokens_since: new Date(),
|
||||
},
|
||||
settings: { ...new UserSettings(), locale: language }
|
||||
});
|
||||
|
||||
await user.save();
|
||||
await user.settings.save();
|
||||
|
||||
setImmediate(async () => {
|
||||
if (Config.get().guild.autoJoin.enabled) {
|
||||
for (const guild of Config.get().guild.autoJoin.guilds || []) {
|
||||
await Member.addToGuild(user.id, guild).catch((e) => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
export const CUSTOM_USER_FLAG_OFFSET = BigInt(1) << BigInt(32);
|
||||
|
||||
export class UserFlags extends BitField {
|
||||
static FLAGS = {
|
||||
DISCORD_EMPLOYEE: BigInt(1) << BigInt(0),
|
||||
PARTNERED_SERVER_OWNER: BigInt(1) << BigInt(1),
|
||||
HYPESQUAD_EVENTS: BigInt(1) << BigInt(2),
|
||||
BUGHUNTER_LEVEL_1: BigInt(1) << BigInt(3),
|
||||
MFA_SMS: BigInt(1) << BigInt(4),
|
||||
PREMIUM_PROMO_DISMISSED: BigInt(1) << BigInt(5),
|
||||
HOUSE_BRAVERY: BigInt(1) << BigInt(6),
|
||||
HOUSE_BRILLIANCE: BigInt(1) << BigInt(7),
|
||||
HOUSE_BALANCE: BigInt(1) << BigInt(8),
|
||||
EARLY_SUPPORTER: BigInt(1) << BigInt(9),
|
||||
TEAM_USER: BigInt(1) << BigInt(10),
|
||||
TRUST_AND_SAFETY: BigInt(1) << BigInt(11),
|
||||
SYSTEM: BigInt(1) << BigInt(12),
|
||||
HAS_UNREAD_URGENT_MESSAGES: BigInt(1) << BigInt(13),
|
||||
BUGHUNTER_LEVEL_2: BigInt(1) << BigInt(14),
|
||||
UNDERAGE_DELETED: BigInt(1) << BigInt(15),
|
||||
VERIFIED_BOT: BigInt(1) << BigInt(16),
|
||||
EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17),
|
||||
CERTIFIED_MODERATOR: BigInt(1) << BigInt(18),
|
||||
BOT_HTTP_INTERACTIONS: BigInt(1) << BigInt(19),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity("groups")
|
||||
export class UserGroup extends BaseClass {
|
||||
@Column()
|
||||
color: number;
|
||||
|
||||
@Column()
|
||||
hoist: boolean;
|
||||
|
||||
@JoinColumn({ name: "controller", referencedColumnName: "id" })
|
||||
@ManyToOne(() => User)
|
||||
controller?: User;
|
||||
|
||||
@Column()
|
||||
mentionable_by?: string;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
rights: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
icon: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
parent?: string;
|
||||
|
||||
@Column({ type: "simple-array", nullable: true})
|
||||
associciations: string[];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Column, Entity, JoinColumn } from "typeorm";
|
||||
import { BaseClassWithoutId, PrimaryIdColumn } from ".";
|
||||
|
||||
@Entity("user_settings")
|
||||
export class UserSettings extends BaseClassWithoutId {
|
||||
@PrimaryIdColumn()
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
afk_timeout: number = 3600;
|
||||
|
||||
@Column({ nullable: true })
|
||||
allow_accessibility_detection: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
animate_emoji: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
animate_stickers: number = 0;
|
||||
|
||||
@Column({ nullable: true })
|
||||
contact_sync_enabled: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
convert_emoticons: boolean = false;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
custom_status: CustomStatus | null = null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
default_guilds_restricted: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
detect_platform_accounts: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
developer_mode: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
disable_games_tab: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
enable_tts_command: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
explicit_content_filter: number = 0;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
friend_source_flags: FriendSourceFlags = { all: true };
|
||||
|
||||
@Column({ nullable: true })
|
||||
gateway_connected: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
gif_auto_play: boolean = false;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder"
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
guild_positions: string[] = []; // guild ids ordered by position
|
||||
|
||||
@Column({ nullable: true })
|
||||
inline_attachment_media: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
inline_embed_media: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
locale: string = "en-US"; // en_US
|
||||
|
||||
@Column({ nullable: true })
|
||||
message_display_compact: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
native_phone_integration_enabled: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
render_embeds: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
render_reactions: boolean = true;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
restricted_guilds: string[] = [];
|
||||
|
||||
@Column({ nullable: true })
|
||||
show_current_game: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online";
|
||||
|
||||
@Column({ nullable: true })
|
||||
stream_notifications_enabled: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
theme: "dark" | "white" = "dark"; // dark
|
||||
|
||||
@Column({ nullable: true })
|
||||
timezone_offset: number = 0; // e.g -60
|
||||
}
|
||||
|
||||
interface CustomStatus {
|
||||
emoji_id?: string;
|
||||
emoji_name?: string;
|
||||
expires_at?: number;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
interface GuildFolder {
|
||||
color: number;
|
||||
guild_ids: string[];
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface FriendSourceFlags {
|
||||
all: boolean
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
import { Guild } from "./Guild";
|
||||
import { User } from "./User";
|
||||
import { Member } from "./Member";
|
||||
|
||||
//https://gist.github.com/vassjozsef/e482c65df6ee1facaace8b3c9ff66145#file-voice_state-ex
|
||||
@Entity("voice_states")
|
||||
export class VoiceState extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
@RelationId((voice_state: VoiceState) => voice_state.guild)
|
||||
guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild?: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((voice_state: VoiceState) => voice_state.channel)
|
||||
channel_id: string;
|
||||
|
||||
@JoinColumn({ name: "channel_id" })
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
channel: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((voice_state: VoiceState) => voice_state.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
|
||||
// @JoinColumn([{ name: "user_id", referencedColumnName: "id" },{ name: "guild_id", referencedColumnName: "guild_id" }])
|
||||
// @ManyToOne(() => Member, {
|
||||
// onDelete: "CASCADE",
|
||||
// })
|
||||
//TODO find a way to make it work without breaking Guild.voice_states
|
||||
member: Member;
|
||||
|
||||
@Column()
|
||||
session_id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
token: string;
|
||||
|
||||
@Column()
|
||||
deaf: boolean;
|
||||
|
||||
@Column()
|
||||
mute: boolean;
|
||||
|
||||
@Column()
|
||||
self_deaf: boolean;
|
||||
|
||||
@Column()
|
||||
self_mute: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
self_stream?: boolean;
|
||||
|
||||
@Column()
|
||||
self_video: boolean;
|
||||
|
||||
@Column()
|
||||
suppress: boolean; // whether this user is muted by the current user
|
||||
|
||||
@Column({ nullable: true, default: null })
|
||||
request_to_speak_timestamp?: Date;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { Application } from "./Application";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
import { Guild } from "./Guild";
|
||||
import { User } from "./User";
|
||||
|
||||
export enum WebhookType {
|
||||
Incoming = 1,
|
||||
ChannelFollower = 2,
|
||||
}
|
||||
|
||||
@Entity("webhooks")
|
||||
export class Webhook extends BaseClass {
|
||||
@Column({ type: "int" })
|
||||
type: WebhookType;
|
||||
|
||||
@Column({ nullable: true })
|
||||
name?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
avatar?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
token?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((webhook: Webhook) => webhook.guild)
|
||||
guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
guild: Guild;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((webhook: Webhook) => webhook.channel)
|
||||
channel_id: string;
|
||||
|
||||
@JoinColumn({ name: "channel_id" })
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
channel: Channel;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((webhook: Webhook) => webhook.application)
|
||||
application_id: string;
|
||||
|
||||
@JoinColumn({ name: "application_id" })
|
||||
@ManyToOne(() => Application, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
application: Application;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((webhook: Webhook) => webhook.user)
|
||||
user_id: string;
|
||||
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
user: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((webhook: Webhook) => webhook.guild)
|
||||
source_guild_id: string;
|
||||
|
||||
@JoinColumn({ name: "source_guild_id" })
|
||||
@ManyToOne(() => Guild, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
source_guild: Guild;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export * from "./Application";
|
||||
export * from "./Attachment";
|
||||
export * from "./AuditLog";
|
||||
export * from "./Ban";
|
||||
export * from "./BaseClass";
|
||||
export * from "./Categories";
|
||||
export * from "./Channel";
|
||||
export * from "./Config";
|
||||
export * from "./ConnectedAccount";
|
||||
export * from "./Emoji";
|
||||
export * from "./Guild";
|
||||
export * from "./Invite";
|
||||
export * from "./Member";
|
||||
export * from "./Message";
|
||||
export * from "./Migration";
|
||||
export * from "./RateLimit";
|
||||
export * from "./ReadState";
|
||||
export * from "./Recipient";
|
||||
export * from "./Relationship";
|
||||
export * from "./Role";
|
||||
export * from "./Session";
|
||||
export * from "./Sticker";
|
||||
export * from "./StickerPack";
|
||||
export * from "./Team";
|
||||
export * from "./TeamMember";
|
||||
export * from "./Template";
|
||||
export * from "./User";
|
||||
export * from "./VoiceState";
|
||||
export * from "./Webhook";
|
||||
export * from "./ClientRelease";
|
||||
export * from "./BackupCodes";
|
||||
export * from "./Note";
|
||||
export * from "./UserSettings";
|
||||
@@ -0,0 +1,9 @@
|
||||
import "reflect-metadata";
|
||||
|
||||
export * from "./util/index";
|
||||
export * from "./config/index";
|
||||
export * from "./interfaces/index";
|
||||
export * from "./entities/index";
|
||||
export * from "./dtos/index";
|
||||
export * from "./util/MFA";
|
||||
export * from "./schemas";
|
||||
@@ -0,0 +1,44 @@
|
||||
export interface Activity {
|
||||
name: string; // the activity's name
|
||||
type: ActivityType; // activity type // TODO: check if its between range 0-5
|
||||
url?: string; // stream url, is validated when type is 1
|
||||
created_at?: number; // unix timestamp of when the activity was added to the user's session
|
||||
timestamps?: {
|
||||
// unix timestamps for start and/or end of the game
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
application_id?: string; // application id for the game
|
||||
details?: string;
|
||||
state?: string;
|
||||
emoji?: {
|
||||
name: string;
|
||||
id?: string;
|
||||
animated: boolean;
|
||||
};
|
||||
party?: {
|
||||
id?: string;
|
||||
size?: [number]; // used to show the party's current and maximum size // TODO: array length 2
|
||||
};
|
||||
assets?: {
|
||||
large_image?: string; // the id for a large asset of the activity, usually a snowflake
|
||||
large_text?: string; // text displayed when hovering over the large image of the activity
|
||||
small_image?: string; // the id for a small asset of the activity, usually a snowflake
|
||||
small_text?: string; // text displayed when hovering over the small image of the activity
|
||||
};
|
||||
secrets?: {
|
||||
join?: string; // the secret for joining a party
|
||||
spectate?: string; // the secret for spectating a game
|
||||
match?: string; // the secret for a specific instanced match
|
||||
};
|
||||
instance?: boolean;
|
||||
flags: string; // activity flags OR d together, describes what the payload includes
|
||||
}
|
||||
|
||||
export enum ActivityType {
|
||||
GAME = 0,
|
||||
STREAMING = 1,
|
||||
LISTENING = 2,
|
||||
CUSTOM = 4,
|
||||
COMPETING = 5,
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
import { PublicUser, User } from "../entities/User";
|
||||
import { Channel } from "../entities/Channel";
|
||||
import { Guild } from "../entities/Guild";
|
||||
import { Member, PublicMember, UserGuildSettings } from "../entities/Member";
|
||||
import { Emoji } from "../entities/Emoji";
|
||||
import { Role } from "../entities/Role";
|
||||
import { Invite } from "../entities/Invite";
|
||||
import { Message, PartialEmoji } from "../entities/Message";
|
||||
import { VoiceState } from "../entities/VoiceState";
|
||||
import { ApplicationCommand } from "../entities/Application";
|
||||
import { Interaction } from "./Interaction";
|
||||
import { ConnectedAccount } from "../entities/ConnectedAccount";
|
||||
import { Relationship, RelationshipType } from "../entities/Relationship";
|
||||
import { Presence } from "./Presence";
|
||||
import { Sticker, UserSettings } from "..";
|
||||
import { Activity, Status } from ".";
|
||||
|
||||
export interface Event {
|
||||
guild_id?: string;
|
||||
user_id?: string;
|
||||
channel_id?: string;
|
||||
created_at?: Date;
|
||||
event: EVENT;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
// ! Custom Events that shouldn't get sent to the client but processed by the server
|
||||
|
||||
export interface InvalidatedEvent extends Event {
|
||||
event: "INVALIDATED";
|
||||
}
|
||||
|
||||
export interface PublicRelationship {
|
||||
id: string;
|
||||
user: PublicUser;
|
||||
type: RelationshipType;
|
||||
}
|
||||
|
||||
// ! END Custom Events that shouldn't get sent to the client but processed by the server
|
||||
|
||||
export interface ReadyEventData {
|
||||
v: number;
|
||||
user: PublicUser & {
|
||||
mobile: boolean;
|
||||
desktop: boolean;
|
||||
email: string | undefined;
|
||||
flags: string;
|
||||
mfa_enabled: boolean;
|
||||
nsfw_allowed: boolean;
|
||||
phone: string | undefined;
|
||||
premium: boolean;
|
||||
premium_type: number;
|
||||
verified: boolean;
|
||||
bot: boolean;
|
||||
};
|
||||
private_channels: Channel[]; // this will be empty for bots
|
||||
session_id: string; // resuming
|
||||
guilds: Guild[];
|
||||
analytics_token?: string;
|
||||
connected_accounts?: ConnectedAccount[];
|
||||
consents?: {
|
||||
personalization?: {
|
||||
consented?: boolean;
|
||||
};
|
||||
};
|
||||
country_code?: string; // e.g. DE
|
||||
friend_suggestion_count?: number;
|
||||
geo_ordered_rtc_regions?: string[]; // ["europe","russie","india","us-east","us-central"]
|
||||
experiments?: [number, number, number, number, number][];
|
||||
guild_experiments?: [
|
||||
// ? what are guild_experiments?
|
||||
// this is the structure of it:
|
||||
number,
|
||||
null,
|
||||
number,
|
||||
[[number, { e: number; s: number }[]]],
|
||||
[number, [[number, [number, number]]]],
|
||||
{ b: number; k: bigint[] }[]
|
||||
][];
|
||||
guild_join_requests?: any[]; // ? what is this? this is new
|
||||
shard?: [number, number];
|
||||
user_settings?: UserSettings;
|
||||
relationships?: PublicRelationship[]; // TODO
|
||||
read_state: {
|
||||
entries: any[]; // TODO
|
||||
partial: boolean;
|
||||
version: number;
|
||||
};
|
||||
user_guild_settings?: {
|
||||
entries: UserGuildSettings[];
|
||||
version: number;
|
||||
partial: boolean;
|
||||
};
|
||||
application?: {
|
||||
id: string;
|
||||
flags: number;
|
||||
};
|
||||
merged_members?: PublicMember[][];
|
||||
// probably all users who the user is in contact with
|
||||
users?: PublicUser[];
|
||||
}
|
||||
|
||||
export interface ReadyEvent extends Event {
|
||||
event: "READY";
|
||||
data: ReadyEventData;
|
||||
}
|
||||
|
||||
export interface ChannelCreateEvent extends Event {
|
||||
event: "CHANNEL_CREATE";
|
||||
data: Channel;
|
||||
}
|
||||
|
||||
export interface ChannelUpdateEvent extends Event {
|
||||
event: "CHANNEL_UPDATE";
|
||||
data: Channel;
|
||||
}
|
||||
|
||||
export interface ChannelDeleteEvent extends Event {
|
||||
event: "CHANNEL_DELETE";
|
||||
data: Channel;
|
||||
}
|
||||
|
||||
export interface ChannelPinsUpdateEvent extends Event {
|
||||
event: "CHANNEL_PINS_UPDATE";
|
||||
data: {
|
||||
guild_id?: string;
|
||||
channel_id: string;
|
||||
last_pin_timestamp?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChannelRecipientAddEvent extends Event {
|
||||
event: "CHANNEL_RECIPIENT_ADD";
|
||||
data: {
|
||||
channel_id: string;
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChannelRecipientRemoveEvent extends Event {
|
||||
event: "CHANNEL_RECIPIENT_REMOVE";
|
||||
data: {
|
||||
channel_id: string;
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildCreateEvent extends Event {
|
||||
event: "GUILD_CREATE";
|
||||
data: Guild & {
|
||||
joined_at: Date;
|
||||
// TODO: add them to guild
|
||||
guild_scheduled_events: never[];
|
||||
guild_hashes: {};
|
||||
presences: never[];
|
||||
stage_instances: never[];
|
||||
threads: never[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildUpdateEvent extends Event {
|
||||
event: "GUILD_UPDATE";
|
||||
data: Guild;
|
||||
}
|
||||
|
||||
export interface GuildDeleteEvent extends Event {
|
||||
event: "GUILD_DELETE";
|
||||
data: {
|
||||
id: string;
|
||||
unavailable?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildBanAddEvent extends Event {
|
||||
event: "GUILD_BAN_ADD";
|
||||
data: {
|
||||
guild_id: string;
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildBanRemoveEvent extends Event {
|
||||
event: "GUILD_BAN_REMOVE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildEmojisUpdateEvent extends Event {
|
||||
event: "GUILD_EMOJIS_UPDATE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
emojis: Emoji[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildStickersUpdateEvent extends Event {
|
||||
event: "GUILD_STICKERS_UPDATE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
stickers: Sticker[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildIntegrationUpdateEvent extends Event {
|
||||
event: "GUILD_INTEGRATIONS_UPDATE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildMemberAddEvent extends Event {
|
||||
event: "GUILD_MEMBER_ADD";
|
||||
data: PublicMember & {
|
||||
guild_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildMemberRemoveEvent extends Event {
|
||||
event: "GUILD_MEMBER_REMOVE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildMemberUpdateEvent extends Event {
|
||||
event: "GUILD_MEMBER_UPDATE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
roles: string[];
|
||||
user: User;
|
||||
nick?: string;
|
||||
joined_at?: Date;
|
||||
premium_since?: number;
|
||||
pending?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildMembersChunkEvent extends Event {
|
||||
event: "GUILD_MEMBERS_CHUNK";
|
||||
data: {
|
||||
guild_id: string;
|
||||
members: PublicMember[];
|
||||
chunk_index: number;
|
||||
chunk_count: number;
|
||||
not_found: string[];
|
||||
presences: Presence[];
|
||||
nonce?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildRoleCreateEvent extends Event {
|
||||
event: "GUILD_ROLE_CREATE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
role: Role;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildRoleUpdateEvent extends Event {
|
||||
event: "GUILD_ROLE_UPDATE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
role: Role;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildRoleDeleteEvent extends Event {
|
||||
event: "GUILD_ROLE_DELETE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
role_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface InviteCreateEvent extends Event {
|
||||
event: "INVITE_CREATE";
|
||||
data: Omit<Invite, "guild" | "channel"> & {
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface InviteDeleteEvent extends Event {
|
||||
event: "INVITE_DELETE";
|
||||
data: {
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
code: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageCreateEvent extends Event {
|
||||
event: "MESSAGE_CREATE";
|
||||
data: Message;
|
||||
}
|
||||
|
||||
export interface MessageUpdateEvent extends Event {
|
||||
event: "MESSAGE_UPDATE";
|
||||
data: Message;
|
||||
}
|
||||
|
||||
export interface MessageDeleteEvent extends Event {
|
||||
event: "MESSAGE_DELETE";
|
||||
data: {
|
||||
id: string;
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageDeleteBulkEvent extends Event {
|
||||
event: "MESSAGE_DELETE_BULK";
|
||||
data: {
|
||||
ids: string[];
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageReactionAddEvent extends Event {
|
||||
event: "MESSAGE_REACTION_ADD";
|
||||
data: {
|
||||
user_id: string;
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id?: string;
|
||||
member?: PublicMember;
|
||||
emoji: PartialEmoji;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageReactionRemoveEvent extends Event {
|
||||
event: "MESSAGE_REACTION_REMOVE";
|
||||
data: {
|
||||
user_id: string;
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id?: string;
|
||||
emoji: PartialEmoji;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageReactionRemoveAllEvent extends Event {
|
||||
event: "MESSAGE_REACTION_REMOVE_ALL";
|
||||
data: {
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageReactionRemoveEmojiEvent extends Event {
|
||||
event: "MESSAGE_REACTION_REMOVE_EMOJI";
|
||||
data: {
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id?: string;
|
||||
emoji: PartialEmoji;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PresenceUpdateEvent extends Event {
|
||||
event: "PRESENCE_UPDATE";
|
||||
data: Presence;
|
||||
}
|
||||
|
||||
export interface TypingStartEvent extends Event {
|
||||
event: "TYPING_START";
|
||||
data: {
|
||||
channel_id: string;
|
||||
user_id: string;
|
||||
timestamp: number;
|
||||
guild_id?: string;
|
||||
member?: PublicMember;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserUpdateEvent extends Event {
|
||||
event: "USER_UPDATE";
|
||||
data: User;
|
||||
}
|
||||
|
||||
export interface VoiceStateUpdateEvent extends Event {
|
||||
event: "VOICE_STATE_UPDATE";
|
||||
data: VoiceState & {
|
||||
member: PublicMember;
|
||||
};
|
||||
}
|
||||
|
||||
export interface VoiceServerUpdateEvent extends Event {
|
||||
event: "VOICE_SERVER_UPDATE";
|
||||
data: {
|
||||
token: string;
|
||||
guild_id: string;
|
||||
endpoint: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WebhooksUpdateEvent extends Event {
|
||||
event: "WEBHOOKS_UPDATE";
|
||||
data: {
|
||||
guild_id: string;
|
||||
channel_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ApplicationCommandPayload = ApplicationCommand & {
|
||||
guild_id: string;
|
||||
};
|
||||
|
||||
export interface ApplicationCommandCreateEvent extends Event {
|
||||
event: "APPLICATION_COMMAND_CREATE";
|
||||
data: ApplicationCommandPayload;
|
||||
}
|
||||
|
||||
export interface ApplicationCommandUpdateEvent extends Event {
|
||||
event: "APPLICATION_COMMAND_UPDATE";
|
||||
data: ApplicationCommandPayload;
|
||||
}
|
||||
|
||||
export interface ApplicationCommandDeleteEvent extends Event {
|
||||
event: "APPLICATION_COMMAND_DELETE";
|
||||
data: ApplicationCommandPayload;
|
||||
}
|
||||
|
||||
export interface InteractionCreateEvent extends Event {
|
||||
event: "INTERACTION_CREATE";
|
||||
data: Interaction;
|
||||
}
|
||||
|
||||
export interface MessageAckEvent extends Event {
|
||||
event: "MESSAGE_ACK";
|
||||
data: {
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
version?: number;
|
||||
manual?: boolean;
|
||||
mention_count?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RelationshipAddEvent extends Event {
|
||||
event: "RELATIONSHIP_ADD";
|
||||
data: PublicRelationship & {
|
||||
should_notify?: boolean;
|
||||
user: PublicUser;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RelationshipRemoveEvent extends Event {
|
||||
event: "RELATIONSHIP_REMOVE";
|
||||
data: Omit<PublicRelationship, "nickname">;
|
||||
}
|
||||
|
||||
export interface SessionsReplace extends Event {
|
||||
event: "SESSIONS_REPLACE";
|
||||
data: {
|
||||
activities: Activity[];
|
||||
client_info: {
|
||||
version: number;
|
||||
os: string;
|
||||
client: string;
|
||||
};
|
||||
status: Status;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface GuildMemberListUpdate extends Event {
|
||||
event: "GUILD_MEMBER_LIST_UPDATE";
|
||||
data: {
|
||||
groups: { id: string; count: number }[];
|
||||
guild_id: string;
|
||||
id: string;
|
||||
member_count: number;
|
||||
online_count: number;
|
||||
ops: {
|
||||
index: number;
|
||||
item: {
|
||||
member?: PublicMember & { presence: Presence };
|
||||
group?: { id: string; count: number }[];
|
||||
};
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export type EventData =
|
||||
| InvalidatedEvent
|
||||
| ReadyEvent
|
||||
| ChannelCreateEvent
|
||||
| ChannelUpdateEvent
|
||||
| ChannelDeleteEvent
|
||||
| ChannelPinsUpdateEvent
|
||||
| ChannelRecipientAddEvent
|
||||
| ChannelRecipientRemoveEvent
|
||||
| GuildCreateEvent
|
||||
| GuildUpdateEvent
|
||||
| GuildDeleteEvent
|
||||
| GuildBanAddEvent
|
||||
| GuildBanRemoveEvent
|
||||
| GuildEmojisUpdateEvent
|
||||
| GuildIntegrationUpdateEvent
|
||||
| GuildMemberAddEvent
|
||||
| GuildMemberRemoveEvent
|
||||
| GuildMemberUpdateEvent
|
||||
| GuildMembersChunkEvent
|
||||
| GuildMemberListUpdate
|
||||
| GuildRoleCreateEvent
|
||||
| GuildRoleUpdateEvent
|
||||
| GuildRoleDeleteEvent
|
||||
| InviteCreateEvent
|
||||
| InviteDeleteEvent
|
||||
| MessageCreateEvent
|
||||
| MessageUpdateEvent
|
||||
| MessageDeleteEvent
|
||||
| MessageDeleteBulkEvent
|
||||
| MessageReactionAddEvent
|
||||
| MessageReactionRemoveEvent
|
||||
| MessageReactionRemoveAllEvent
|
||||
| MessageReactionRemoveEmojiEvent
|
||||
| PresenceUpdateEvent
|
||||
| TypingStartEvent
|
||||
| UserUpdateEvent
|
||||
| VoiceStateUpdateEvent
|
||||
| VoiceServerUpdateEvent
|
||||
| WebhooksUpdateEvent
|
||||
| ApplicationCommandCreateEvent
|
||||
| ApplicationCommandUpdateEvent
|
||||
| ApplicationCommandDeleteEvent
|
||||
| InteractionCreateEvent
|
||||
| MessageAckEvent
|
||||
| RelationshipAddEvent
|
||||
| RelationshipRemoveEvent;
|
||||
|
||||
// located in collection events
|
||||
|
||||
export enum EVENTEnum {
|
||||
Ready = "READY",
|
||||
ChannelCreate = "CHANNEL_CREATE",
|
||||
ChannelUpdate = "CHANNEL_UPDATE",
|
||||
ChannelDelete = "CHANNEL_DELETE",
|
||||
ChannelPinsUpdate = "CHANNEL_PINS_UPDATE",
|
||||
ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD",
|
||||
ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE",
|
||||
GuildCreate = "GUILD_CREATE",
|
||||
GuildUpdate = "GUILD_UPDATE",
|
||||
GuildDelete = "GUILD_DELETE",
|
||||
GuildBanAdd = "GUILD_BAN_ADD",
|
||||
GuildBanRemove = "GUILD_BAN_REMOVE",
|
||||
GuildEmojUpdate = "GUILD_EMOJI_UPDATE",
|
||||
GuildIntegrationsUpdate = "GUILD_INTEGRATIONS_UPDATE",
|
||||
GuildMemberAdd = "GUILD_MEMBER_ADD",
|
||||
GuildMemberRempve = "GUILD_MEMBER_REMOVE",
|
||||
GuildMemberUpdate = "GUILD_MEMBER_UPDATE",
|
||||
GuildMemberSpeaking = "GUILD_MEMBER_SPEAKING",
|
||||
GuildMembersChunk = "GUILD_MEMBERS_CHUNK",
|
||||
GuildMemberListUpdate = "GUILD_MEMBER_LIST_UPDATE",
|
||||
GuildRoleCreate = "GUILD_ROLE_CREATE",
|
||||
GuildRoleDelete = "GUILD_ROLE_DELETE",
|
||||
GuildRoleUpdate = "GUILD_ROLE_UPDATE",
|
||||
InviteCreate = "INVITE_CREATE",
|
||||
InviteDelete = "INVITE_DELETE",
|
||||
MessageCreate = "MESSAGE_CREATE",
|
||||
MessageUpdate = "MESSAGE_UPDATE",
|
||||
MessageDelete = "MESSAGE_DELETE",
|
||||
MessageDeleteBulk = "MESSAGE_DELETE_BULK",
|
||||
MessageReactionAdd = "MESSAGE_REACTION_ADD",
|
||||
MessageReactionRemove = "MESSAGE_REACTION_REMOVE",
|
||||
MessageReactionRemoveAll = "MESSAGE_REACTION_REMOVE_ALL",
|
||||
MessageReactionRemoveEmoji = "MESSAGE_REACTION_REMOVE_EMOJI",
|
||||
PresenceUpdate = "PRESENCE_UPDATE",
|
||||
TypingStart = "TYPING_START",
|
||||
UserUpdate = "USER_UPDATE",
|
||||
WebhooksUpdate = "WEBHOOKS_UPDATE",
|
||||
InteractionCreate = "INTERACTION_CREATE",
|
||||
VoiceStateUpdate = "VOICE_STATE_UPDATE",
|
||||
VoiceServerUpdate = "VOICE_SERVER_UPDATE",
|
||||
ApplicationCommandCreate = "APPLICATION_COMMAND_CREATE",
|
||||
ApplicationCommandUpdate = "APPLICATION_COMMAND_UPDATE",
|
||||
ApplicationCommandDelete = "APPLICATION_COMMAND_DELETE",
|
||||
SessionsReplace = "SESSIONS_REPLACE",
|
||||
}
|
||||
|
||||
export type EVENT =
|
||||
| "READY"
|
||||
| "CHANNEL_CREATE"
|
||||
| "CHANNEL_UPDATE"
|
||||
| "CHANNEL_DELETE"
|
||||
| "CHANNEL_PINS_UPDATE"
|
||||
| "CHANNEL_RECIPIENT_ADD"
|
||||
| "CHANNEL_RECIPIENT_REMOVE"
|
||||
| "GUILD_CREATE"
|
||||
| "GUILD_UPDATE"
|
||||
| "GUILD_DELETE"
|
||||
| "GUILD_BAN_ADD"
|
||||
| "GUILD_BAN_REMOVE"
|
||||
| "GUILD_EMOJIS_UPDATE"
|
||||
| "GUILD_STICKERS_UPDATE"
|
||||
| "GUILD_INTEGRATIONS_UPDATE"
|
||||
| "GUILD_MEMBER_ADD"
|
||||
| "GUILD_MEMBER_REMOVE"
|
||||
| "GUILD_MEMBER_UPDATE"
|
||||
| "GUILD_MEMBER_SPEAKING"
|
||||
| "GUILD_MEMBERS_CHUNK"
|
||||
| "GUILD_MEMBER_LIST_UPDATE"
|
||||
| "GUILD_ROLE_CREATE"
|
||||
| "GUILD_ROLE_DELETE"
|
||||
| "GUILD_ROLE_UPDATE"
|
||||
| "INVITE_CREATE"
|
||||
| "INVITE_DELETE"
|
||||
| "MESSAGE_CREATE"
|
||||
| "MESSAGE_UPDATE"
|
||||
| "MESSAGE_DELETE"
|
||||
| "MESSAGE_DELETE_BULK"
|
||||
| "MESSAGE_REACTION_ADD"
|
||||
// TODO: add a new event: bulk add reaction:
|
||||
// | "MESSAGE_REACTION_BULK_ADD"
|
||||
| "MESSAGE_REACTION_REMOVE"
|
||||
| "MESSAGE_REACTION_REMOVE_ALL"
|
||||
| "MESSAGE_REACTION_REMOVE_EMOJI"
|
||||
| "PRESENCE_UPDATE"
|
||||
| "TYPING_START"
|
||||
| "USER_UPDATE"
|
||||
| "USER_NOTE_UPDATE"
|
||||
| "WEBHOOKS_UPDATE"
|
||||
| "INTERACTION_CREATE"
|
||||
| "VOICE_STATE_UPDATE"
|
||||
| "VOICE_SERVER_UPDATE"
|
||||
| "APPLICATION_COMMAND_CREATE"
|
||||
| "APPLICATION_COMMAND_UPDATE"
|
||||
| "APPLICATION_COMMAND_DELETE"
|
||||
| "MESSAGE_ACK"
|
||||
| "RELATIONSHIP_ADD"
|
||||
| "RELATIONSHIP_REMOVE"
|
||||
| "SESSIONS_REPLACE"
|
||||
| CUSTOMEVENTS;
|
||||
|
||||
export type CUSTOMEVENTS = "INVALIDATED" | "RATELIMIT";
|
||||
@@ -0,0 +1,34 @@
|
||||
import { AllowedMentions, Embed } from "../entities/Message";
|
||||
|
||||
export interface Interaction {
|
||||
id: string;
|
||||
type: InteractionType;
|
||||
data?: {};
|
||||
guild_id: string;
|
||||
channel_id: string;
|
||||
member_id: string;
|
||||
token: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export enum InteractionType {
|
||||
SelfCommand = 0,
|
||||
Ping = 1,
|
||||
ApplicationCommand = 2,
|
||||
}
|
||||
|
||||
export enum InteractionResponseType {
|
||||
SelfCommandResponse = 0,
|
||||
Pong = 1,
|
||||
Acknowledge = 2,
|
||||
ChannelMessage = 3,
|
||||
ChannelMessageWithSource = 4,
|
||||
AcknowledgeWithSource = 5,
|
||||
}
|
||||
|
||||
export interface InteractionApplicationCommandCallbackData {
|
||||
tts?: boolean;
|
||||
content: string;
|
||||
embeds?: Embed[];
|
||||
allowed_mentions?: AllowedMentions;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ClientStatus, Status } from "./Status";
|
||||
import { Activity } from "./Activity";
|
||||
import { PublicUser } from "../entities/User";
|
||||
|
||||
export interface Presence {
|
||||
user: PublicUser;
|
||||
guild_id?: string;
|
||||
status: Status;
|
||||
activities: Activity[];
|
||||
client_status: ClientStatus;
|
||||
// TODO: game
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export type Status = "idle" | "dnd" | "online" | "offline" | "invisible";
|
||||
|
||||
export interface ClientStatus {
|
||||
desktop?: string; // e.g. Windows/Linux/Mac
|
||||
mobile?: string; // e.g. iOS/Android
|
||||
web?: string; // e.g. browser, bot account
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./Activity";
|
||||
export * from "./Presence";
|
||||
export * from "./Interaction";
|
||||
export * from "./Event";
|
||||
export * from "./Status";
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class premiumSinceAsDate1659921859145 implements MigrationInterface {
|
||||
name = 'premiumSinceAsDate1659921859145'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`members\` DROP COLUMN \`premium_since\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`members\`
|
||||
ADD \`premium_since\` datetime NULL
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`members\` DROP COLUMN \`premium_since\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`members\`
|
||||
ADD \`premium_since\` bigint NULL
|
||||
`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class CodeCleanup31660258393551 implements MigrationInterface {
|
||||
name = 'CodeCleanup31660258393551'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_2ce5a55796fe4c2f77ece57a647\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
DROP INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE \`user_settings\` (
|
||||
\`id\` varchar(255) NOT NULL,
|
||||
\`afk_timeout\` int NULL,
|
||||
\`allow_accessibility_detection\` tinyint NULL,
|
||||
\`animate_emoji\` tinyint NULL,
|
||||
\`animate_stickers\` int NULL,
|
||||
\`contact_sync_enabled\` tinyint NULL,
|
||||
\`convert_emoticons\` tinyint NULL,
|
||||
\`custom_status\` text NULL,
|
||||
\`default_guilds_restricted\` tinyint NULL,
|
||||
\`detect_platform_accounts\` tinyint NULL,
|
||||
\`developer_mode\` tinyint NULL,
|
||||
\`disable_games_tab\` tinyint NULL,
|
||||
\`enable_tts_command\` tinyint NULL,
|
||||
\`explicit_content_filter\` int NULL,
|
||||
\`friend_source_flags\` text NULL,
|
||||
\`gateway_connected\` tinyint NULL,
|
||||
\`gif_auto_play\` tinyint NULL,
|
||||
\`guild_folders\` text NULL,
|
||||
\`guild_positions\` text NULL,
|
||||
\`inline_attachment_media\` tinyint NULL,
|
||||
\`inline_embed_media\` tinyint NULL,
|
||||
\`locale\` varchar(255) NULL,
|
||||
\`message_display_compact\` tinyint NULL,
|
||||
\`native_phone_integration_enabled\` tinyint NULL,
|
||||
\`render_embeds\` tinyint NULL,
|
||||
\`render_reactions\` tinyint NULL,
|
||||
\`restricted_guilds\` text NULL,
|
||||
\`show_current_game\` tinyint NULL,
|
||||
\`status\` varchar(255) NULL,
|
||||
\`stream_notifications_enabled\` tinyint NULL,
|
||||
\`theme\` varchar(255) NULL,
|
||||
\`timezone_offset\` int NULL,
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE = InnoDB
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`users\` DROP COLUMN \`settings\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`type\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`hook\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`redirect_uris\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`rpc_application_state\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`store_application_state\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`verification_state\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`interactions_endpoint_url\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`integration_public\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`integration_require_code_grant\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`discoverability_state\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`discovery_eligibility_flags\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`tags\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`install_params\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`bot_user_id\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`guilds\`
|
||||
ADD \`premium_progress_bar_enabled\` tinyint NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`rpc_origins\` text NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`primary_sku_id\` varchar(255) NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`slug\` varchar(255) NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`guild_id\` varchar(255) NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NOT NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`flags\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`flags\` varchar(255) NOT NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD CONSTRAINT \`FK_e5bf78cdbbe9ba91062d74c5aba\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_e5bf78cdbbe9ba91062d74c5aba\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`flags\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`flags\` int NOT NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`guild_id\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`slug\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`primary_sku_id\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\` DROP COLUMN \`rpc_origins\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`guilds\` DROP COLUMN \`premium_progress_bar_enabled\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`bot_user_id\` varchar(255) NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`install_params\` text NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`tags\` text NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`discovery_eligibility_flags\` int NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`discoverability_state\` int NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`integration_require_code_grant\` tinyint NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`integration_public\` tinyint NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`interactions_endpoint_url\` varchar(255) NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`verification_state\` int NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`store_application_state\` int NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`rpc_application_state\` int NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`redirect_uris\` text NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`hook\` tinyint NOT NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD \`type\` text NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`users\`
|
||||
ADD \`settings\` text NOT NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
DROP TABLE \`user_settings\`
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\` (\`bot_user_id\`)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE \`applications\`
|
||||
ADD CONSTRAINT \`FK_2ce5a55796fe4c2f77ece57a647\` FOREIGN KEY (\`bot_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||
`);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user