Merge branch 'master' into maddyrtc
This commit is contained in:
commit
0bbeca9237
@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"url": "https://api.fosscord.com/v{version}",
|
"url": "https://api.fosscord.com/api/v{version}",
|
||||||
"description": "Official fosscord instance",
|
"description": "Official fosscord instance",
|
||||||
"variables": {
|
"variables": {
|
||||||
"version": {
|
"version": {
|
||||||
@ -2960,7 +2960,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"verifie": {
|
"verified": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"visibility": {
|
"visibility": {
|
||||||
@ -2980,7 +2980,7 @@
|
|||||||
"type",
|
"type",
|
||||||
"user",
|
"user",
|
||||||
"user_id",
|
"user_id",
|
||||||
"verifie",
|
"verified",
|
||||||
"visibility"
|
"visibility"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
12
api/assets/preload-plugins/fosscord-login.js
Normal file
12
api/assets/preload-plugins/fosscord-login.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Remove `<link id="logincss" rel="stylesheet" href="/assets/fosscord-login.css" />` from header when we're not accessing `/login` or `/register`
|
||||||
|
// fosscord-login.css replaces discord's TOS tooltip with something more fitting for fosscord, which when included in the main app, causes other tooltips
|
||||||
|
// to be affected, which is potentially unwanted.
|
||||||
|
//
|
||||||
|
// This script removes fosscord-login.css when a user reloads the page. From testing, it appears fosscord already properly removes
|
||||||
|
// fosscord-login.css after login is successful, but not if you reload the page after logging in. This script is to remove fosscord-login.css in
|
||||||
|
// that specific case.
|
||||||
|
|
||||||
|
var token = JSON.parse(localStorage.getItem("token"));
|
||||||
|
if (!token && location.pathname !== "/login" && location.pathname !== "/register") {
|
||||||
|
document.getElementById("logincss").remove();
|
||||||
|
}
|
@ -355,11 +355,11 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"verifie": {
|
"verified": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "type", "verifie"]
|
"required": ["name", "type", "verified"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||||
|
@ -9,11 +9,19 @@ const InviteRegex = /\W/g;
|
|||||||
|
|
||||||
router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
|
router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
|
const guild = await Guild.findOneOrFail({ id: guild_id });
|
||||||
|
|
||||||
|
if (!guild.features.includes("ALIASABLE_NAMES")) {
|
||||||
const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } });
|
const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } });
|
||||||
if (!invite) return res.json({ code: null });
|
if (!invite) return res.json({ code: null });
|
||||||
|
|
||||||
return res.json({ code: invite.code, uses: invite.uses });
|
return res.json({ code: invite.code, uses: invite.uses });
|
||||||
|
} else {
|
||||||
|
const invite = await Invite.find({ where: { guild_id: guild_id, vanity_url: true } });
|
||||||
|
if (!invite || invite.length == 0) return res.json({ code: null });
|
||||||
|
|
||||||
|
return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface VanityUrlSchema {
|
export interface VanityUrlSchema {
|
||||||
@ -24,18 +32,33 @@ export interface VanityUrlSchema {
|
|||||||
code?: string;
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check if guild is elgible for vanity url
|
|
||||||
router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
|
router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const body = req.body as VanityUrlSchema;
|
const body = req.body as VanityUrlSchema;
|
||||||
const code = body.code?.replace(InviteRegex, "");
|
const code = body.code?.replace(InviteRegex, "");
|
||||||
|
|
||||||
|
const guild = await Guild.findOneOrFail({ id: guild_id });
|
||||||
|
if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
|
||||||
|
|
||||||
|
if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
|
||||||
|
|
||||||
const invite = await Invite.findOne({ code });
|
const invite = await Invite.findOne({ code });
|
||||||
if (invite) throw new HTTPError("Invite already exists");
|
if (invite) throw new HTTPError("Invite already exists");
|
||||||
|
|
||||||
const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT });
|
const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT });
|
||||||
|
|
||||||
await Invite.update({ vanity_url: true, guild_id }, { code: code, channel_id: id });
|
await new Invite({
|
||||||
|
vanity_url: true,
|
||||||
|
code: code,
|
||||||
|
temporary: false,
|
||||||
|
uses: 0,
|
||||||
|
max_uses: 0,
|
||||||
|
max_age: 0,
|
||||||
|
created_at: new Date(),
|
||||||
|
expires_at: new Date(),
|
||||||
|
guild_id: guild_id,
|
||||||
|
channel_id: id
|
||||||
|
}).save();
|
||||||
|
|
||||||
return res.json({ code: code });
|
return res.json({ code: code });
|
||||||
});
|
});
|
||||||
|
@ -82,11 +82,13 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
|||||||
if (opts.message_reference) {
|
if (opts.message_reference) {
|
||||||
permission.hasThrow("READ_MESSAGE_HISTORY");
|
permission.hasThrow("READ_MESSAGE_HISTORY");
|
||||||
// code below has to be redone when we add custom message routing and cross-channel replies
|
// code below has to be redone when we add custom message routing and cross-channel replies
|
||||||
|
if (message.guild_id !== null) {
|
||||||
const guild = await Guild.findOneOrFail({ id: channel.guild_id });
|
const guild = await Guild.findOneOrFail({ id: channel.guild_id });
|
||||||
if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
|
if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
|
||||||
if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
|
if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
|
||||||
if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
|
if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// TODO: should be checked if the referenced message exists?
|
// TODO: should be checked if the referenced message exists?
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
message.type = MessageType.REPLY;
|
message.type = MessageType.REPLY;
|
||||||
|
@ -14,12 +14,12 @@ import { Webhook } from "./Webhook";
|
|||||||
import { DmChannelDTO } from "../dtos";
|
import { DmChannelDTO } from "../dtos";
|
||||||
|
|
||||||
export enum ChannelType {
|
export enum ChannelType {
|
||||||
GUILD_TEXT = 0, // a text channel within a server
|
GUILD_TEXT = 0, // a text channel within a guild
|
||||||
DM = 1, // a direct message between users
|
DM = 1, // a direct message between users
|
||||||
GUILD_VOICE = 2, // a voice channel within a server
|
GUILD_VOICE = 2, // a voice channel within a guild
|
||||||
GROUP_DM = 3, // a direct message between multiple users
|
GROUP_DM = 3, // a direct message between multiple users
|
||||||
GUILD_CATEGORY = 4, // an organizational category that contains up to 50 channels
|
GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels
|
||||||
GUILD_NEWS = 5, // a channel that users can follow and crosspost into their own server
|
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 game on Discord
|
GUILD_STORE = 6, // a channel in which game developers can sell their game on Discord
|
||||||
ENCRYPTED = 7, // end-to-end encrypted channel
|
ENCRYPTED = 7, // end-to-end encrypted channel
|
||||||
ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel
|
ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel
|
||||||
@ -72,7 +72,7 @@ export class Channel extends BaseClass {
|
|||||||
@ManyToOne(() => Channel)
|
@ManyToOne(() => Channel)
|
||||||
parent?: Channel;
|
parent?: Channel;
|
||||||
|
|
||||||
// only for group dms
|
// for group DMs and owned custom channel types
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@RelationId((channel: Channel) => channel.owner)
|
@RelationId((channel: Channel) => channel.owner)
|
||||||
owner_id: string;
|
owner_id: string;
|
||||||
@ -117,6 +117,9 @@ export class Channel extends BaseClass {
|
|||||||
})
|
})
|
||||||
invites?: Invite[];
|
invites?: Invite[];
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
retention_policy_id?: string;
|
||||||
|
|
||||||
@OneToMany(() => Message, (message: Message) => message.channel, {
|
@OneToMany(() => Message, (message: Message) => message.channel, {
|
||||||
cascade: true,
|
cascade: true,
|
||||||
orphanedRowAction: "delete",
|
orphanedRowAction: "delete",
|
||||||
@ -182,6 +185,7 @@ export class Channel extends BaseClass {
|
|||||||
|
|
||||||
switch (channel.type) {
|
switch (channel.type) {
|
||||||
case ChannelType.GUILD_TEXT:
|
case ChannelType.GUILD_TEXT:
|
||||||
|
case ChannelType.GUILD_NEWS:
|
||||||
case ChannelType.GUILD_VOICE:
|
case ChannelType.GUILD_VOICE:
|
||||||
if (channel.parent_id && !opts?.skipExistsCheck) {
|
if (channel.parent_id && !opts?.skipExistsCheck) {
|
||||||
const exists = await Channel.findOneOrFail({ id: channel.parent_id });
|
const exists = await Channel.findOneOrFail({ id: channel.parent_id });
|
||||||
@ -191,25 +195,24 @@ export class Channel extends BaseClass {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ChannelType.GUILD_CATEGORY:
|
case ChannelType.GUILD_CATEGORY:
|
||||||
|
case ChannelType.UNHANDLED:
|
||||||
break;
|
break;
|
||||||
case ChannelType.DM:
|
case ChannelType.DM:
|
||||||
case ChannelType.GROUP_DM:
|
case ChannelType.GROUP_DM:
|
||||||
throw new HTTPError("You can't create a dm channel in a guild");
|
throw new HTTPError("You can't create a dm channel in a guild");
|
||||||
// TODO: check if guild is community server
|
|
||||||
case ChannelType.GUILD_STORE:
|
case ChannelType.GUILD_STORE:
|
||||||
case ChannelType.GUILD_NEWS:
|
|
||||||
default:
|
default:
|
||||||
throw new HTTPError("Not yet supported");
|
throw new HTTPError("Not yet supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!channel.permission_overwrites) channel.permission_overwrites = [];
|
if (!channel.permission_overwrites) channel.permission_overwrites = [];
|
||||||
// TODO: auto generate position
|
// TODO: eagerly auto generate position of all guild channels
|
||||||
|
|
||||||
channel = {
|
channel = {
|
||||||
...channel,
|
...channel,
|
||||||
...(!opts?.keepId && { id: Snowflake.generate() }),
|
...(!opts?.keepId && { id: Snowflake.generate() }),
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
position: channel.position || 0,
|
position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -231,11 +234,13 @@ export class Channel extends BaseClass {
|
|||||||
const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
|
const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
|
||||||
|
|
||||||
// TODO: check config for max number of recipients
|
// TODO: check config for max number of recipients
|
||||||
|
/** if you want to disallow note to self channels, uncomment the conditional below
|
||||||
if (otherRecipientsUsers.length !== recipients.length) {
|
if (otherRecipientsUsers.length !== recipients.length) {
|
||||||
throw new HTTPError("Recipient/s not found");
|
throw new HTTPError("Recipient/s not found");
|
||||||
}
|
}
|
||||||
|
**/
|
||||||
|
|
||||||
const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
|
const type = recipients.length > 1 ? ChannelType.DM : ChannelType.GROUP_DM;
|
||||||
|
|
||||||
let channel = null;
|
let channel = null;
|
||||||
|
|
||||||
@ -288,7 +293,8 @@ export class Channel extends BaseClass {
|
|||||||
await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
|
await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
return channel_dto.excludedRecipients([creator_user_id]);
|
if (recipients.length === 1) return channel_dto;
|
||||||
|
else return channel_dto.excludedRecipients([creator_user_id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeRecipientFromChannel(channel: Channel, user_id: string) {
|
static async removeRecipientFromChannel(channel: Channel, user_id: string) {
|
||||||
@ -354,4 +360,5 @@ export interface ChannelPermissionOverwrite {
|
|||||||
export enum ChannelPermissionOverwriteType {
|
export enum ChannelPermissionOverwriteType {
|
||||||
role = 0,
|
role = 0,
|
||||||
member = 1,
|
member = 1,
|
||||||
|
group = 2,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
|||||||
import { BaseClass } from "./BaseClass";
|
import { BaseClass } from "./BaseClass";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
|
|
||||||
export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verifie"> {}
|
export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verified"> {}
|
||||||
|
|
||||||
@Entity("connected_accounts")
|
@Entity("connected_accounts")
|
||||||
export class ConnectedAccount extends BaseClass {
|
export class ConnectedAccount extends BaseClass {
|
||||||
@ -35,7 +35,7 @@ export class ConnectedAccount extends BaseClass {
|
|||||||
type: string;
|
type: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
verifie: boolean;
|
verified: boolean;
|
||||||
|
|
||||||
@Column({ select: false })
|
@Column({ select: false })
|
||||||
visibility: number;
|
visibility: number;
|
||||||
|
@ -85,8 +85,8 @@ export class Member extends BaseClassWithoutId {
|
|||||||
@Column()
|
@Column()
|
||||||
joined_at: Date;
|
joined_at: Date;
|
||||||
|
|
||||||
@Column({ type: "bigint", nullable: true })
|
@Column()
|
||||||
premium_since?: number;
|
premium_since?: Date;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
deaf: boolean;
|
deaf: boolean;
|
||||||
@ -245,7 +245,7 @@ export class Member extends BaseClassWithoutId {
|
|||||||
nick: undefined,
|
nick: undefined,
|
||||||
roles: [guild_id], // @everyone role
|
roles: [guild_id], // @everyone role
|
||||||
joined_at: new Date(),
|
joined_at: new Date(),
|
||||||
premium_since: (new Date()).getTime(),
|
premium_since: new Date(),
|
||||||
deaf: false,
|
deaf: false,
|
||||||
mute: false,
|
mute: false,
|
||||||
pending: false,
|
pending: false,
|
||||||
|
@ -12,11 +12,13 @@ export interface Interaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum InteractionType {
|
export enum InteractionType {
|
||||||
|
SelfCommand = 0,
|
||||||
Ping = 1,
|
Ping = 1,
|
||||||
ApplicationCommand = 2,
|
ApplicationCommand = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum InteractionResponseType {
|
export enum InteractionResponseType {
|
||||||
|
SelfCommandResponse = 0,
|
||||||
Pong = 1,
|
Pong = 1,
|
||||||
Acknowledge = 2,
|
Acknowledge = 2,
|
||||||
ChannelMessage = 3,
|
ChannelMessage = 3,
|
||||||
|
@ -65,6 +65,8 @@ export class Rights extends BitField {
|
|||||||
// inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user
|
// inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user
|
||||||
SELF_ADD_DISCOVERABLE: BitFlag(36), // can mark discoverable guilds that they have permissions to mark as discoverable
|
SELF_ADD_DISCOVERABLE: BitFlag(36), // can mark discoverable guilds that they have permissions to mark as discoverable
|
||||||
MANAGE_GUILD_DIRECTORY: BitFlag(37), // can change anything in the primary guild directory
|
MANAGE_GUILD_DIRECTORY: BitFlag(37), // can change anything in the primary guild directory
|
||||||
|
POGGERS: BitFlag(38), // can send confetti, screenshake, random user mention (@someone)
|
||||||
|
USE_ACHIEVEMENTS: BitFlag(39), // can use achievements and cheers
|
||||||
INITIATE_INTERACTIONS: BitFlag(40), // can initiate interactions
|
INITIATE_INTERACTIONS: BitFlag(40), // can initiate interactions
|
||||||
RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions
|
RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions
|
||||||
SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events
|
SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events
|
||||||
|
Loading…
x
Reference in New Issue
Block a user