Clean up array extensions

This commit is contained in:
Rory& 2025-10-03 20:50:48 +02:00
parent 8bd0d5c06b
commit a25393e806
10 changed files with 76 additions and 34 deletions

View File

@ -118,7 +118,7 @@
"@spacebar/gateway": "dist/gateway", "@spacebar/gateway": "dist/gateway",
"@spacebar/util": "dist/util", "@spacebar/util": "dist/util",
"@spacebar/webrtc": "dist/webrtc", "@spacebar/webrtc": "dist/webrtc",
"lambert-server": "dist/util/util/lambert-server" "lambert-server": "dist/util/util/lambert-server"
}, },
"optionalDependencies": { "optionalDependencies": {
"@sendgrid/mail": "^8.1.6", "@sendgrid/mail": "^8.1.6",

View File

@ -51,7 +51,7 @@ router.put(
const recipients = [ const recipients = [
...(channel.recipients?.map((r) => r.user_id) || []), ...(channel.recipients?.map((r) => r.user_id) || []),
user_id, user_id,
].unique(); ].distinct();
const new_channel = await Channel.createDMChannel( const new_channel = await Channel.createDMChannel(
recipients, recipients,

View File

@ -35,7 +35,7 @@ router.post(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
const user_id = req.user_id; const user_id = req.user_id;
const timestamp = Date.nowSeconds(); const timestamp = Math.floor(Date.now() / 1000);
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
where: { id: channel_id }, where: { id: channel_id },
}); });

View File

@ -17,7 +17,7 @@
*/ */
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { DiscordApiErrors, Member, partition } from "@spacebar/util"; import { DiscordApiErrors, Member } from "@spacebar/util";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
@ -38,8 +38,7 @@ router.patch(
relations: ["roles"], relations: ["roles"],
}); });
const [add, remove] = partition( const [add, remove] = members.partition(
members,
(member) => (member) =>
member_ids.includes(member.id) && member_ids.includes(member.id) &&
!member.roles.map((role) => role.id).includes(role_id), !member.roles.map((role) => role.id).includes(role_id),

View File

@ -20,7 +20,7 @@ import { Config } from "@spacebar/util";
const reNUMBER = /[0-9]/g; const reNUMBER = /[0-9]/g;
const reUPPERCASELETTER = /[A-Z]/g; const reUPPERCASELETTER = /[A-Z]/g;
const reSYMBOLS = /[A-Z,a-z,0-9]/g; const reSYMBOLS = /[A-Za-z0-9]/g;
// const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored in db // const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored in db
/* /*
@ -45,12 +45,12 @@ export function checkPassword(password: string): number {
} }
// checks for amount of Numbers // checks for amount of Numbers
if (password.count(reNUMBER) >= minNumbers - 1) { if (password.match(reNUMBER)?.length ?? 0 >= minNumbers - 1) {
strength += 0.05; strength += 0.05;
} }
// checks for amount of Uppercase Letters // checks for amount of Uppercase Letters
if (password.count(reUPPERCASELETTER) >= minUpperCase - 1) { if (password.match(reUPPERCASELETTER)?.length ?? 0 >= minUpperCase - 1) {
strength += 0.05; strength += 0.05;
} }
@ -61,8 +61,8 @@ export function checkPassword(password: string): number {
// checks if password only consists of numbers or only consists of chars // checks if password only consists of numbers or only consists of chars
if ( if (
password.length == password.count(reNUMBER) || password.length == password.match(reNUMBER)?.length ||
password.length === password.count(reUPPERCASELETTER) password.length === password.match(reUPPERCASELETTER)?.length
) { ) {
strength = 0; strength = 0;
} }

View File

@ -26,7 +26,6 @@ import {
LazyRequestSchema, LazyRequestSchema,
User, User,
Presence, Presence,
partition,
Channel, Channel,
Permissions, Permissions,
} from "@spacebar/util"; } from "@spacebar/util";
@ -108,7 +107,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
const member_roles = members const member_roles = members
.map((m) => m.roles) .map((m) => m.roles)
.flat() .flat()
.unique((r: Role) => r.id); .distinctBy((r: Role) => r.id);
member_roles.push( member_roles.push(
member_roles.splice( member_roles.splice(
member_roles.findIndex((x) => x.id === x.guild_id), member_roles.findIndex((x) => x.id === x.guild_id),
@ -119,8 +118,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
const offlineItems = []; const offlineItems = [];
for (const role of member_roles) { for (const role of member_roles) {
const [role_members, other_members] = partition( const [role_members, other_members] = members.partition(
members,
(m: Member) => !!m.roles.find((r) => r.id === role.id), (m: Member) => !!m.roles.find((r) => r.id === role.id),
); );
const group = { const group = {
@ -284,9 +282,9 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
channel.permission_overwrites.forEach((overwrite) => { channel.permission_overwrites.forEach((overwrite) => {
const { id, allow, deny } = overwrite; const { id, allow, deny } = overwrite;
if (allow.toBigInt() & Permissions.FLAGS.VIEW_CHANNEL) if (BigInt(allow) & Permissions.FLAGS.VIEW_CHANNEL)
perms.push(`allow:${id}`); perms.push(`allow:${id}`);
else if (deny.toBigInt() & Permissions.FLAGS.VIEW_CHANNEL) else if (BigInt(deny) & Permissions.FLAGS.VIEW_CHANNEL)
perms.push(`deny:${id}`); perms.push(`deny:${id}`);
}); });
@ -307,7 +305,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
const groups = ops const groups = ops
.map((x) => x.groups) .map((x) => x.groups)
.flat() .flat()
.unique(); .distinct();
await Send(this, { await Send(this, {
op: OPCODES.Dispatch, op: OPCODES.Dispatch,

View File

@ -27,7 +27,7 @@ import {
} from "typeorm"; } from "typeorm";
import { DmChannelDTO } from "../dtos"; import { DmChannelDTO } from "../dtos";
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
import { InvisibleCharacters, Snowflake, containsAll, emitEvent, getPermission, trimSpecial, Permissions, BitField } from "../util"; import { InvisibleCharacters, Snowflake, emitEvent, getPermission, trimSpecial, Permissions, BitField } from "../util";
import { BaseClass } from "./BaseClass"; import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild"; import { Guild } from "./Guild";
import { Invite } from "./Invite"; import { Invite } from "./Invite";
@ -328,7 +328,7 @@ export class Channel extends BaseClass {
creator_user_id: string, creator_user_id: string,
name?: string, name?: string,
) { ) {
recipients = recipients.unique().filter((x) => x !== creator_user_id); recipients = recipients.distinct().filter((x) => x !== creator_user_id);
// 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 you want to disallow note to self channels, uncomment the conditional below
@ -354,7 +354,7 @@ export class Channel extends BaseClass {
if (!ur.channel.recipients) continue; if (!ur.channel.recipients) continue;
const re = ur.channel.recipients.map((r) => r.user_id); const re = ur.channel.recipients.map((r) => r.user_id);
if (re.length === channelRecipients.length) { if (re.length === channelRecipients.length) {
if (containsAll(re, channelRecipients)) { if (re.containsAll(channelRecipients)) {
if (channel == null) { if (channel == null) {
channel = ur.channel; channel = ur.channel;
await ur.assign({ closed: false }).save(); await ur.assign({ closed: false }).save();

View File

@ -56,9 +56,9 @@ export function enableAutoUpdate(opts: {
const latestVersion = await getLatestVersion(opts.packageJsonLink); const latestVersion = await getLatestVersion(opts.packageJsonLink);
if (currentVersion !== latestVersion) { if (currentVersion !== latestVersion) {
rl.question( rl.question(
`[Auto Update] Current version (${currentVersion}) is out of date, would you like to update? (yes/no)`, `[Auto Update] Current version (${currentVersion}) is out of date, would you like to update? (Y/n)`,
(answer) => { (answer) => {
if (answer.toBoolean()) { if (answer === "" || answer.toLowerCase() === "y") {
console.log(`[Auto update] updating ...`); console.log(`[Auto update] updating ...`);
download(opts.downloadUrl, opts.path); download(opts.downloadUrl, opts.path);
} else { } else {

View File

@ -23,7 +23,7 @@ export function FieldErrors(
return new FieldError( return new FieldError(
50035, 50035,
"Invalid Form Body", "Invalid Form Body",
fields.map(({ message, code }) => ({ (fields as Object).map(({ message, code }) => ({
_errors: [ _errors: [
{ {
message, message,

View File

@ -23,57 +23,102 @@ declare global {
single(filter: (elem: T) => boolean): T | null; single(filter: (elem: T) => boolean): T | null;
forEachAsync(callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void>; forEachAsync(callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void>;
remove(item: T): void; remove(item: T): void;
first(): T | undefined;
last(): T | undefined;
distinct(): T[];
distinctBy<K>(key: (elem: T) => K): T[];
} }
} }
export function containsAll<T>(arr: T[], target: T[]) { export function arrayContainsAll<T>(arr: T[], target: T[]) {
return target.every((v) => arr.includes(v)); return target.every((v) => arr.includes(v));
} }
/* https://stackoverflow.com/a/50636286 */ /* https://stackoverflow.com/a/50636286 */
export function partition<T>(array: T[], filter: (elem: T) => boolean): [T[], T[]] { export function arrayPartition<T>(array: T[], filter: (elem: T) => boolean): [T[], T[]] {
const pass: T[] = [], const pass: T[] = [],
fail: T[] = []; fail: T[] = [];
array.forEach((e) => (filter(e) ? pass : fail).push(e)); array.forEach((e) => (filter(e) ? pass : fail).push(e));
return [pass, fail]; return [pass, fail];
} }
export function single<T>(array: T[], filter: (elem: T) => boolean): T | null { export function arraySingle<T>(array: T[], filter: (elem: T) => boolean): T | null {
const results = array.filter(filter); const results = array.filter(filter);
if (results.length > 1) throw new Error("Array contains more than one matching element"); if (results.length > 1) throw new Error("Array contains more than one matching element");
if (results.length === 0) return null; if (results.length === 0) return null;
return results[0]; return results[0];
} }
export async function forEachAsync<T>(array: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void> { export async function arrayForEachAsync<T>(array: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void> {
await Promise.all(array.map(callback)); await Promise.all(array.map(callback));
} }
export function remove<T>(this: T[], item: T): void { export function arrayRemove<T>(this: T[], item: T): void {
const index = this.indexOf(item); const index = this.indexOf(item);
if (index > -1) { if (index > -1) {
this.splice(index, 1); this.splice(index, 1);
} }
} }
export function arrayFirst<T>(this: T[]): T | undefined {
return this[0];
}
export function arrayLast<T>(this: T[]): T | undefined {
return this[this.length - 1];
}
export function arrayDistinct<T>(this: T[]): T[] {
return Array.from(new Set(this));
}
export function arrayDistinctBy<T, K>(this: T[], key: (elem: T) => K): T[] {
const seen = new Set<K>();
return this.filter((item) => {
const k = key(item);
if (seen.has(k)) {
return false;
} else {
seen.add(k);
return true;
}
});
}
// register extensions // register extensions
if (!Array.prototype.containsAll) if (!Array.prototype.containsAll)
Array.prototype.containsAll = function <T>(this: T[], target: T[]) { Array.prototype.containsAll = function <T>(this: T[], target: T[]) {
return containsAll(this, target); return arrayContainsAll(this, target);
}; };
if (!Array.prototype.partition) if (!Array.prototype.partition)
Array.prototype.partition = function <T>(this: T[], filter: (elem: T) => boolean) { Array.prototype.partition = function <T>(this: T[], filter: (elem: T) => boolean) {
return partition(this, filter); return arrayPartition(this, filter);
}; };
if (!Array.prototype.single) if (!Array.prototype.single)
Array.prototype.single = function <T>(this: T[], filter: (elem: T) => boolean) { Array.prototype.single = function <T>(this: T[], filter: (elem: T) => boolean) {
return single(this, filter); return arraySingle(this, filter);
}; };
if (!Array.prototype.forEachAsync) if (!Array.prototype.forEachAsync)
Array.prototype.forEachAsync = function <T>(this: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>) { Array.prototype.forEachAsync = function <T>(this: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>) {
return forEachAsync(this, callback); return arrayForEachAsync(this, callback);
}; };
if (!Array.prototype.remove) if (!Array.prototype.remove)
Array.prototype.remove = function <T>(this: T[], item: T) { Array.prototype.remove = function <T>(this: T[], item: T) {
return remove.call(this, item); return arrayRemove.call(this, item);
};
if (!Array.prototype.first)
Array.prototype.first = function <T>(this: T[]) {
return arrayFirst.call(this);
};
if (!Array.prototype.last)
Array.prototype.last = function <T>(this: T[]) {
return arrayLast.call(this);
};
if (!Array.prototype.distinct)
Array.prototype.distinct = function <T>(this: T[]) {
return arrayDistinct.call(this);
};
if (!Array.prototype.distinctBy)
Array.prototype.distinctBy = function <T, K>(this: T[], key: (elem: T) => K) {
return arrayDistinctBy.call(this, key as ((elem: unknown) => unknown));
}; };