Merge branch 'master' into slowcord
This commit is contained in:
		
						commit
						6ffe277f2f
					
				
							
								
								
									
										45
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -1,7 +1,40 @@ | ||||
| FROM node:14 | ||||
| WORKDIR /usr/src/fosscord-server/ | ||||
| COPY . . | ||||
| WORKDIR /usr/src/fosscord-server/bundle | ||||
| FROM node:alpine | ||||
| 
 | ||||
| # env vars | ||||
| ENV WORK_DIR="/srv/fosscord-server" | ||||
| ENV DEV_MODE=0 | ||||
| ENV HTTP_PORT=3001 | ||||
| ENV WS_PORT=3002 | ||||
| ENV CDN_PORT=3003 | ||||
| ENV RTC_PORT=3004 | ||||
| ENV ADMIN_PORT=3005 | ||||
| 
 | ||||
| # exposed ports (only for reference, see https://docs.docker.com/engine/reference/builder/#expose) | ||||
| EXPOSE ${HTTP_PORT}/tcp ${WS_PORT}/tcp ${CDN_PORT}/tcp ${RTC_PORT}/tcp ${ADMIN_PORT}/tcp | ||||
| 
 | ||||
| # install required apps | ||||
| RUN apk add --no-cache --update git python2 py-pip make build-base | ||||
| 
 | ||||
| # optionl: packages for debugging/development | ||||
| RUN apk add --no-cache sqlite | ||||
| 
 | ||||
| # download fosscord-server | ||||
| WORKDIR $WORK_DIR/src | ||||
| RUN git clone https://github.com/fosscord/fosscord-server.git . | ||||
| 
 | ||||
| # setup and run | ||||
| WORKDIR $WORK_DIR/src/bundle | ||||
| RUN npm run setup | ||||
| EXPOSE 3001 | ||||
| CMD [ "npm", "run", "start:bundle" ] | ||||
| RUN npm install @yukikaze-bot/erlpack | ||||
| # RUN npm install mysql --save | ||||
| 
 | ||||
| # create update script | ||||
| RUN printf '#!/bin/sh\n\ngit -C $WORK_DIR/src/ checkout master\ngit -C $WORK_DIR/src/ reset --hard HEAD\ngit -C $WORK_DIR/src/ pull\ncd $WORK_DIR/src/bundle/\nnpm run setup\n' > $WORK_DIR/update.sh | ||||
| RUN chmod +x $WORK_DIR/update.sh | ||||
| 
 | ||||
| # configure entrypoint file | ||||
| RUN printf '#!/bin/sh\n\nDEV_MODE=${DEV_MODE:-0}\n\nif [ "$DEV_MODE" -eq 1 ]; then\n    tail -f /dev/null\nelse\n    cd $WORK_DIR/src/bundle/\n    npm run start:bundle\nfi\n' > $WORK_DIR/entrypoint.sh | ||||
| RUN chmod +x $WORK_DIR/entrypoint.sh | ||||
| 
 | ||||
| WORKDIR $WORK_DIR | ||||
| ENTRYPOINT ["./entrypoint.sh"] | ||||
|  | ||||
| @ -3119,7 +3119,7 @@ | ||||
| 						"type": "boolean" | ||||
| 					}, | ||||
| 					"status": { | ||||
| 						"enum": ["dnd", "idle", "offline", "online"], | ||||
| 						"enum": ["dnd", "idle", "offline", "online", "invisible"], | ||||
| 						"type": "string" | ||||
| 					}, | ||||
| 					"stream_notifications_enabled": { | ||||
| @ -5677,7 +5677,7 @@ | ||||
| 						"type": "boolean" | ||||
| 					}, | ||||
| 					"status": { | ||||
| 						"enum": ["dnd", "idle", "offline", "online"], | ||||
| 						"enum": ["dnd", "idle", "offline", "online", "invisible"], | ||||
| 						"type": "string" | ||||
| 					}, | ||||
| 					"stream_notifications_enabled": { | ||||
|  | ||||
| @ -7900,7 +7900,7 @@ | ||||
| 				"type": "boolean" | ||||
| 			}, | ||||
| 			"status": { | ||||
| 				"enum": ["dnd", "idle", "offline", "online"], | ||||
| 				"enum": ["dnd", "idle", "offline", "online", "invisible"], | ||||
| 				"type": "string" | ||||
| 			}, | ||||
| 			"stream_notifications_enabled": { | ||||
|  | ||||
| @ -33,17 +33,32 @@ router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: | ||||
| 	const { guild_id } = req.params; | ||||
| 
 | ||||
| 	let bans = await Ban.find({ guild_id: guild_id }); | ||||
| 	let promisesToAwait: object[] = []; | ||||
| 	const bansObj: object[] = []; | ||||
| 
 | ||||
| 	/* Filter secret from database registry.*/ | ||||
| 	bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
 | ||||
| 
 | ||||
| 	bans.filter(ban => ban.user_id !== ban.executor_id); | ||||
| 	// pretend self-bans don't exist to prevent victim chasing
 | ||||
| 	 | ||||
| 	bans.forEach((registry: BanRegistrySchema) => { | ||||
| 	delete registry.ip; | ||||
| 	bans.forEach((ban) => { | ||||
| 		promisesToAwait.push(User.getPublicUser(ban.user_id)); | ||||
| 	}); | ||||
| 	 | ||||
| 	return res.json(bans); | ||||
| 
 | ||||
| 	const bannedUsers: object[] = await Promise.all(promisesToAwait); | ||||
| 
 | ||||
| 	bans.forEach((ban, index) => { | ||||
| 		const user = bannedUsers[index] as User; | ||||
| 		bansObj.push({ | ||||
| 			reason: ban.reason, | ||||
| 			user: { | ||||
| 				username: user.username, | ||||
| 				discriminator: user.discriminator, | ||||
| 				id: user.id, | ||||
| 				avatar: user.avatar, | ||||
| 				public_flags: user.public_flags | ||||
| 			} | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	return res.json(bansObj); | ||||
| }); | ||||
| 
 | ||||
| router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { | ||||
|  | ||||
| @ -25,13 +25,19 @@ router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, re | ||||
| 
 | ||||
| 	const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); | ||||
| 	const permission = await getPermission(req.user_id, guild_id); | ||||
| 	const everyone = await Role.findOneOrFail({ guild_id: guild_id, name: "@everyone", position: 0 }); | ||||
| 
 | ||||
| 	if (body.roles) { | ||||
| 		permission.hasThrow("MANAGE_ROLES"); | ||||
| 
 | ||||
| 		if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id); | ||||
| 		member.roles = body.roles.map((x) => new Role({ id: x })); // foreign key constraint will fail if role doesn't exist
 | ||||
| 	} | ||||
| 
 | ||||
| 	await member.save(); | ||||
| 
 | ||||
| 	member.roles = member.roles.filter((x) => x.id !== everyone.id); | ||||
| 
 | ||||
| 	// do not use promise.all as we have to first write to db before emitting the event to catch errors
 | ||||
| 	await emitEvent({ | ||||
| 		event: "GUILD_MEMBER_UPDATE", | ||||
|  | ||||
| @ -9,11 +9,19 @@ const InviteRegex = /\W/g; | ||||
| 
 | ||||
| router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { | ||||
| 	const { guild_id } = req.params; | ||||
| 	const guild = await Guild.findOneOrFail({ id: guild_id }); | ||||
| 
 | ||||
| 	const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } }); | ||||
| 	if (!invite) return res.json({ code: null }); | ||||
| 	if (!guild.features.includes("ALIASABLE_NAMES")) { | ||||
| 		const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } }); | ||||
| 		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 { | ||||
| @ -24,18 +32,33 @@ export interface VanityUrlSchema { | ||||
| 	code?: string; | ||||
| } | ||||
| 
 | ||||
| // TODO: check if guild is elgible for vanity url
 | ||||
| router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { | ||||
| 	const { guild_id } = req.params; | ||||
| 	const body = req.body as VanityUrlSchema; | ||||
| 	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 }); | ||||
| 	if (invite) throw new HTTPError("Invite already exists"); | ||||
| 
 | ||||
| 	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 }); | ||||
| }); | ||||
|  | ||||
| @ -64,12 +64,14 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: | ||||
| 		user.data.hash = await bcrypt.hash(body.new_password, 12); | ||||
| 	} | ||||
| 
 | ||||
| 	var check_username = body?.username?.replace(/\s/g, ''); | ||||
| 	if(!check_username && !body?.avatar && !body?.banner) { | ||||
| 		throw FieldErrors({ | ||||
| 			username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } | ||||
| 		}); | ||||
| 	} | ||||
|     if(body.username){ | ||||
|         var check_username = body?.username?.replace(/\s/g, ''); | ||||
|         if(!check_username) { | ||||
|             throw FieldErrors({ | ||||
|                 username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 	await user.save(); | ||||
| 
 | ||||
|  | ||||
| @ -6,9 +6,9 @@ const router: Router = Router(); | ||||
| router.put("/:id", route({}), async (req: Request, res: Response) => { | ||||
| 	//TODO
 | ||||
| 	res.json({ | ||||
| 		message: "400: Bad Request", | ||||
| 		code: 0 | ||||
| 	}).status(400); | ||||
| 		message: "Unknown User", | ||||
| 		code: 10013 | ||||
| 	}).status(404); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
|  | ||||
| @ -82,10 +82,12 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { | ||||
| 	if (opts.message_reference) { | ||||
| 		permission.hasThrow("READ_MESSAGE_HISTORY"); | ||||
| 		// code below has to be redone when we add custom message routing and cross-channel replies
 | ||||
| 		const guild = await Guild.findOneOrFail({ id: channel.guild_id }); | ||||
| 		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.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); | ||||
| 		if (message.guild_id !== null) { | ||||
| 			const guild = await Guild.findOneOrFail({ id: channel.guild_id }); | ||||
| 			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.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?
 | ||||
| 		// @ts-ignore
 | ||||
|  | ||||
| @ -1,7 +1,47 @@ | ||||
| version: "3" | ||||
| version: '3.8' | ||||
| 
 | ||||
| services: | ||||
|   server: | ||||
|     image: fosscord/server | ||||
|   fosscord: | ||||
|     container_name: fosscord | ||||
|     image: fosscord | ||||
|     restart: on-failure:5 | ||||
|     # depends_on: mariadb | ||||
|     build: . | ||||
|     ports: | ||||
|       - 3001:3001 | ||||
|       - '3001-3005:3001-3005' | ||||
|     volumes: | ||||
|       # - ./data/:${WORK_DIR:-/srv/fosscord-server}/data/ | ||||
|       - data:${WORK_DIR:-/srv/fosscord-server}/ | ||||
|     environment: | ||||
|       WORK_DIR: ${WORK_DIR:-/srv/fosscord-server} | ||||
|       DEV_MODE: ${DEV_MODE:-0} | ||||
|       THREADS: ${THREADS:-1} | ||||
|       DATABASE: ${DATABASE:-../../data/database.db} | ||||
|       STORAGE_LOCATION: ${STORAGE_LOCATION:-../../data/files/} | ||||
|       HTTP_PORT: 3001 | ||||
|       WS_PORT: 3002 | ||||
|       CDN_PORT: 3003 | ||||
|       RTC_PORT: 3004 | ||||
|       ADMIN_PORT: 3005 | ||||
| 
 | ||||
|   # mariadb: | ||||
|   #   image: mariadb:latest | ||||
|   #   restart: on-failure:5 | ||||
|   #   environment: | ||||
|   #     MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-secr3tpassw0rd} | ||||
|   #     MYSQL_DATABASE: ${MYSQL_DATABASE:-fosscord} | ||||
|   #     MYSQL_USER: ${MYSQL_USER:-fosscord} | ||||
|   #     MYSQL_PASSWORD: ${MYSQL_PASSWORD:-password1} | ||||
|   #   networks: | ||||
|   #     - default | ||||
|   #   volumes: | ||||
|   #     - mariadb:/var/lib/mysql | ||||
| 
 | ||||
| volumes: | ||||
|   data: | ||||
|   # mariadb: | ||||
| 
 | ||||
| networks: | ||||
|   default: | ||||
|     name: fosscord | ||||
|     driver: bridge | ||||
|  | ||||
| @ -240,8 +240,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 			x.guild_hashes = {}; // @ts-ignore
 | ||||
| 			x.guild_scheduled_events = []; // @ts-ignore
 | ||||
| 			x.threads = []; | ||||
| 			x.premium_subscription_count = 30; | ||||
| 			x.premium_tier = 3; | ||||
| 			return x; | ||||
| 		}), | ||||
| 		guild_experiments: [], // TODO
 | ||||
|  | ||||
| @ -85,8 +85,8 @@ export class Member extends BaseClassWithoutId { | ||||
| 	@Column() | ||||
| 	joined_at: Date; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	premium_since?: Date; | ||||
| 	@Column({ type: "bigint", nullable: true }) | ||||
| 	premium_since?: number; | ||||
| 
 | ||||
| 	@Column() | ||||
| 	deaf: boolean; | ||||
| @ -245,7 +245,7 @@ export class Member extends BaseClassWithoutId { | ||||
| 			nick: undefined, | ||||
| 			roles: [guild_id], // @everyone role
 | ||||
| 			joined_at: new Date(), | ||||
| 			premium_since: new Date(), | ||||
| 			premium_since: (new Date()).getTime(), | ||||
| 			deaf: false, | ||||
| 			mute: false, | ||||
| 			pending: false, | ||||
|  | ||||
| @ -360,7 +360,7 @@ export interface UserSettings { | ||||
| 	render_reactions: boolean; | ||||
| 	restricted_guilds: string[]; | ||||
| 	show_current_game: boolean; | ||||
| 	status: "online" | "offline" | "dnd" | "idle"; | ||||
| 	status: "online" | "offline" | "dnd" | "idle" | "invisible"; | ||||
| 	stream_notifications_enabled: boolean; | ||||
| 	theme: "dark" | "white"; // dark
 | ||||
| 	timezone_offset: number; // e.g -60
 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| export type Status = "idle" | "dnd" | "online" | "offline"; | ||||
| export type Status = "idle" | "dnd" | "online" | "offline" | "invisible"; | ||||
| 
 | ||||
| export interface ClientStatus { | ||||
| 	desktop?: string; // e.g. Windows/Linux/Mac
 | ||||
|  | ||||
| @ -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
 | ||||
| 		SELF_ADD_DISCOVERABLE: BitFlag(36), // can mark discoverable guilds that they have permissions to mark as discoverable
 | ||||
| 		MANAGE_GUILD_DIRECTORY: BitFlag(37), // can change anything in the primary guild directory
 | ||||
| 		POGGERS: BitFlag(38), // can send confetti, screenshake, random user mention (@someone)
 | ||||
| 		USE_ACHIEVEMENTS: BitFlag(39), // can use achievements and cheers
 | ||||
| 		INITIATE_INTERACTIONS: BitFlag(40), // can initiate interactions
 | ||||
| 		RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions
 | ||||
| 		SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Madeline
						Madeline