remove useless extensions (commits from spacebar)
This commit is contained in:
parent
c8b738f28d
commit
4eb674be85
Binary file not shown.
Binary file not shown.
BIN
package-lock.json
generated
BIN
package-lock.json
generated
Binary file not shown.
@ -74,6 +74,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.936.0",
|
||||
"@spacebarchat/medooze-webrtc": "^1.0.8",
|
||||
"@toondepauw/node-zstd": "^1.2.0",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"ajv": "^8.17.1",
|
||||
|
||||
@ -121,7 +121,7 @@ function apiRoutes(missingRoutes) {
|
||||
const tags = Array.from(routes.keys())
|
||||
.map((x) => getTag(x))
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
specification.tags = tags.distinct().map((x) => ({ name: x }));
|
||||
specification.tags = [...new Set(tags)].map((x) => ({ name: x }));
|
||||
|
||||
routes.forEach((route, pathAndMethod) => {
|
||||
const [p, method] = pathAndMethod.split("|");
|
||||
@ -213,7 +213,7 @@ function apiRoutes(missingRoutes) {
|
||||
obj.parameters = [...(obj.parameters || []), ...query];
|
||||
}
|
||||
|
||||
obj.tags = [...(obj.tags || []), getTag(p)].distinct();
|
||||
obj.tags = [...new Set([...(obj.tags || []), getTag(p)])];
|
||||
|
||||
if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
|
||||
obj["x-badges"] = [
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
arrayRemove,
|
||||
Channel,
|
||||
emitEvent,
|
||||
Emoji,
|
||||
@ -112,7 +113,7 @@ router.delete(
|
||||
|
||||
const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
|
||||
if (!already_added) throw new HTTPError("Reaction not found", 404);
|
||||
message.reactions.remove(already_added);
|
||||
arrayRemove(message.reactions, already_added);
|
||||
|
||||
await Promise.all([
|
||||
message.save(),
|
||||
@ -283,7 +284,7 @@ router.delete(
|
||||
|
||||
already_added.count--;
|
||||
|
||||
if (already_added.count <= 0) message.reactions.remove(already_added);
|
||||
if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
|
||||
else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
|
||||
|
||||
await message.save();
|
||||
@ -340,7 +341,7 @@ router.delete(
|
||||
|
||||
already_added.count--;
|
||||
|
||||
if (already_added.count <= 0) message.reactions.remove(already_added);
|
||||
if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
|
||||
else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
|
||||
|
||||
await message.save();
|
||||
|
||||
@ -39,6 +39,7 @@ import {
|
||||
Relationship,
|
||||
Rights,
|
||||
Snowflake,
|
||||
stringGlobToRegexp,
|
||||
uploadFile,
|
||||
User,
|
||||
} from "@spacebar/util";
|
||||
@ -244,16 +245,19 @@ router.get(
|
||||
return x;
|
||||
});
|
||||
|
||||
await ret
|
||||
await Promise.all(
|
||||
ret
|
||||
.filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user)
|
||||
.forEachAsync(async (x: MessageCreateSchema) => {
|
||||
.map(async (x: MessageCreateSchema) => {
|
||||
x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } });
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// polyfill message references for old messages
|
||||
await ret
|
||||
await Promise.all(
|
||||
ret
|
||||
.filter((msg) => msg.message_reference && !msg.referenced_message?.id)
|
||||
.forEachAsync(async (msg) => {
|
||||
.map(async (msg) => {
|
||||
const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
|
||||
id: msg.message_reference!.message_id,
|
||||
};
|
||||
@ -261,7 +265,8 @@ router.get(
|
||||
if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id;
|
||||
|
||||
msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] });
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return res.json(ret);
|
||||
},
|
||||
@ -449,8 +454,8 @@ router.post(
|
||||
|
||||
if (rule.trigger_type == AutomodTriggerTypes.CUSTOM_WORDS) {
|
||||
const triggerMeta = rule.trigger_metadata as AutomodCustomWordsRule;
|
||||
const regexes = triggerMeta.regex_patterns.map((x) => new RegExp(x, "i")).concat(triggerMeta.keyword_filter.map((k) => k.globToRegexp("i")));
|
||||
const allowedRegexes = triggerMeta.allow_list.map((k) => k.globToRegexp("i"));
|
||||
const regexes = triggerMeta.regex_patterns.map((x) => new RegExp(x, "i")).concat(triggerMeta.keyword_filter.map((k) => stringGlobToRegexp(k, "i")));
|
||||
const allowedRegexes = triggerMeta.allow_list.map((k) => stringGlobToRegexp(k, "i"));
|
||||
|
||||
const matches = regexes
|
||||
.map((r) => message.content!.match(r))
|
||||
|
||||
@ -47,10 +47,7 @@ router.put(
|
||||
});
|
||||
|
||||
if (channel.type !== ChannelType.GROUP_DM) {
|
||||
const recipients = [
|
||||
...(channel.recipients?.map((r) => r.user_id) || []),
|
||||
user_id,
|
||||
].distinct();
|
||||
const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])];
|
||||
|
||||
const new_channel = await Channel.createDMChannel(
|
||||
recipients,
|
||||
|
||||
@ -40,11 +40,13 @@ router.get(
|
||||
relations: PublicInviteRelation,
|
||||
});
|
||||
|
||||
await invites
|
||||
await Promise.all(
|
||||
invites
|
||||
.filter((i) => i.isExpired())
|
||||
.forEachAsync(async (i) => {
|
||||
.map(async (i) => {
|
||||
await Invite.delete({ code: i.code });
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return res.json(invites.filter((i) => !i.isExpired()));
|
||||
},
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
import { Router, Request, Response } from "express";
|
||||
import { DiscordApiErrors, Member } from "@spacebar/util";
|
||||
import { DiscordApiErrors, Member, arrayPartition } from "@spacebar/util";
|
||||
import { route } from "@spacebar/api";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
@ -38,11 +38,7 @@ router.patch(
|
||||
relations: ["roles"],
|
||||
});
|
||||
|
||||
const [add, remove] = members.partition(
|
||||
(member) =>
|
||||
member_ids.includes(member.id) &&
|
||||
!member.roles.map((role) => role.id).includes(role_id),
|
||||
);
|
||||
const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id));
|
||||
|
||||
// TODO (erkin): have a bulk add/remove function that adds the roles in a single txn
|
||||
await Promise.all([
|
||||
|
||||
@ -42,7 +42,7 @@ router.get(
|
||||
await Message.find({
|
||||
where: { channel_id: channel?.id },
|
||||
order: { timestamp: "DESC" },
|
||||
take: Math.clamp(req.query.limit ? Number(req.query.limit) : 50, 1, Config.get().limits.message.maxPreloadCount),
|
||||
take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1, Config.get().limits.message.maxPreloadCount)),
|
||||
})
|
||||
).filter((x) => x !== null) as Message[];
|
||||
|
||||
|
||||
@ -67,10 +67,8 @@ router.patch(
|
||||
relations: ["settings"],
|
||||
});
|
||||
|
||||
if (!user.settings)
|
||||
user.settings = UserSettings.create(body as UserSettingsUpdateSchema);
|
||||
else
|
||||
user.settings.assign(body);
|
||||
if (!user.settings) user.settings = UserSettings.create<UserSettings>(body);
|
||||
else user.settings.assign(body);
|
||||
|
||||
if (body.guild_folders)
|
||||
user.settings.guild_folders = body.guild_folders;
|
||||
|
||||
@ -83,7 +83,7 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) {
|
||||
user_id: this.user_id,
|
||||
data: sessions,
|
||||
} as SessionsReplace);
|
||||
const session = sessions.first() || {
|
||||
const session = sessions[0] || {
|
||||
activities: [],
|
||||
client_status: {},
|
||||
status: "offline",
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
Presence,
|
||||
Channel,
|
||||
Permissions,
|
||||
arrayPartition,
|
||||
} from "@spacebar/util";
|
||||
import {
|
||||
WebSocket,
|
||||
@ -51,16 +52,13 @@ const getMostRelevantSession = (sessions: Session[]) => {
|
||||
invisible: 3,
|
||||
offline: 4,
|
||||
};
|
||||
|
||||
// sort sessions by relevance
|
||||
sessions = sessions.sort((a, b) => {
|
||||
return (
|
||||
statusMap[a.status] -
|
||||
statusMap[b.status] +
|
||||
((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2
|
||||
);
|
||||
return statusMap[a.status] - statusMap[b.status] + ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2;
|
||||
});
|
||||
|
||||
return sessions.first();
|
||||
return sessions[0];
|
||||
};
|
||||
|
||||
async function getMembers(guild_id: string, range: [number, number]) {
|
||||
@ -104,23 +102,19 @@ async function getMembers(guild_id: string, range: [number, number]) {
|
||||
|
||||
const groups = [];
|
||||
const items = [];
|
||||
const member_roles = members
|
||||
const member_roles = [
|
||||
...new Map(
|
||||
members
|
||||
.map((m) => m.roles)
|
||||
.flat()
|
||||
.distinctBy((r: Role) => r.id);
|
||||
member_roles.push(
|
||||
member_roles.splice(
|
||||
member_roles.findIndex((x) => x.id === x.guild_id),
|
||||
1,
|
||||
)[0],
|
||||
);
|
||||
.map((role) => [role.id, role] as [string, Role]),
|
||||
).values(),
|
||||
];
|
||||
|
||||
const offlineItems = [];
|
||||
|
||||
for (const role of member_roles) {
|
||||
const [role_members, other_members] = members.partition(
|
||||
(m: Member) => !!m.roles.find((r) => r.id === role.id),
|
||||
);
|
||||
const [role_members, other_members] = arrayPartition(members, (m: Member) => !!m.roles.find((r) => r.id === role.id));
|
||||
const group = {
|
||||
count: role_members.length,
|
||||
id: role.id === guild_id ? "online" : role.id,
|
||||
@ -257,7 +251,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
|
||||
|
||||
if (!channels) throw new Error("Must provide channel ranges");
|
||||
|
||||
const channel_id = Object.keys(channels || {}).first();
|
||||
const channel_id = Object.keys(channels || {})[0];
|
||||
if (!channel_id) return;
|
||||
|
||||
const permissions = await getPermission(this.user_id, guild_id, channel_id);
|
||||
@ -302,10 +296,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
|
||||
});
|
||||
});
|
||||
|
||||
const groups = ops
|
||||
.map((x) => x.groups)
|
||||
.flat()
|
||||
.distinct();
|
||||
const groups = [...new Set(ops.map((x) => x.groups).flat())];
|
||||
|
||||
await Send(this, {
|
||||
op: OPCODES.Dispatch,
|
||||
|
||||
@ -255,7 +255,7 @@ export class Channel extends BaseClass {
|
||||
}
|
||||
|
||||
static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
|
||||
recipients = recipients.distinct().filter((x) => x !== creator_user_id);
|
||||
recipients = [...new Set(recipients)].filter((x) => x !== creator_user_id);
|
||||
// TODO: check config for max number of recipients
|
||||
/** if you want to disallow note to self channels, uncomment the conditional below
|
||||
|
||||
@ -280,7 +280,7 @@ export class Channel extends BaseClass {
|
||||
if (!ur.channel.recipients) continue;
|
||||
const re = ur.channel.recipients.map((r) => r.user_id);
|
||||
if (re.length === channelRecipients.length) {
|
||||
if (re.containsAll(channelRecipients)) {
|
||||
if (channelRecipients.every((_) => re.includes(_))) {
|
||||
if (channel == null) {
|
||||
channel = ur.channel;
|
||||
await ur.assign({ closed: false }).save();
|
||||
@ -474,7 +474,11 @@ export class Channel extends BaseClass {
|
||||
userPerms = new Permissions(userPerms.remove(overwrite.deny).add(overwrite.allow));
|
||||
|
||||
// member overwrite, throws if somehow we have multiple overwrites for the same member
|
||||
const memberOverwrite = this.permission_overwrites.single((o) => o.type === ChannelPermissionOverwriteType.member && o.id === member?.id);
|
||||
const memberOverwrite = this.permission_overwrites.find(
|
||||
(o) =>
|
||||
o.type === ChannelPermissionOverwriteType.member &&
|
||||
o.id === member?.id
|
||||
);
|
||||
if (memberOverwrite) userPerms = new Permissions(userPerms.remove(memberOverwrite.deny).add(memberOverwrite.allow));
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ import { Template } from "./Template";
|
||||
import { User } from "./User";
|
||||
import { VoiceState } from "./VoiceState";
|
||||
import { Webhook } from "./Webhook";
|
||||
import { arrayRemove } from "@spacebar/util";
|
||||
|
||||
// TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0}
|
||||
// TODO: guild_scheduled_events
|
||||
@ -420,7 +421,7 @@ export class Guild extends BaseClass {
|
||||
if (typeof insertPoint == "string") position = guild.channel_ordering.indexOf(insertPoint) + 1;
|
||||
else position = insertPoint;
|
||||
|
||||
guild.channel_ordering.remove(channel_id);
|
||||
arrayRemove(guild.channel_ordering, channel_id);
|
||||
|
||||
guild.channel_ordering.splice(position, 0, channel_id);
|
||||
await Guild.update({ id: guild_id }, { channel_ordering: guild.channel_ordering });
|
||||
|
||||
@ -354,9 +354,9 @@ export class User extends BaseClass {
|
||||
for (const channel of qry) {
|
||||
console.warn(JSON.stringify(channel));
|
||||
}
|
||||
throw new Error("Array contains more than one matching element");
|
||||
}
|
||||
|
||||
// throw if multiple
|
||||
return qry.single((_) => true);
|
||||
return qry[0];
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
|
||||
return new FieldError(
|
||||
50035,
|
||||
"Invalid Form Body",
|
||||
fields.map<ErrorContent, ObjectErrorContent>(({ message, code }) => ({
|
||||
Object.values(fields).map(({ message, code }) => ({
|
||||
_errors: [
|
||||
{
|
||||
message,
|
||||
@ -39,7 +39,7 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
|
||||
},
|
||||
],
|
||||
})),
|
||||
errors
|
||||
errors,
|
||||
);
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export class FieldError extends Error {
|
||||
public code: string | number,
|
||||
public message: string,
|
||||
public errors?: object, // TODO: I don't like this typing.
|
||||
public _ajvErrors?: ErrorObject[]
|
||||
public _ajvErrors?: ErrorObject[],
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@ -15,12 +15,9 @@ export class KittyLogo {
|
||||
public static async initialise() {
|
||||
this.isSupported = await this.checkSupport();
|
||||
if (this.isSupported)
|
||||
this.iconCache = readFileSync(
|
||||
__dirname + "/../../../assets/icon.png",
|
||||
{
|
||||
this.iconCache = readFileSync(__dirname + "/../../../assets/icon.png", {
|
||||
encoding: "base64",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public static printLogo(): void {
|
||||
@ -77,11 +74,9 @@ export class KittyLogo {
|
||||
if (resp.startsWith("\x1B_Gi=31;OK")) resolve(true);
|
||||
else resolve(false);
|
||||
});
|
||||
process.stdout.write(
|
||||
"\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c",
|
||||
);
|
||||
process.stdout.write("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c");
|
||||
|
||||
await sleep(5000);
|
||||
await new Promise((res) => setTimeout(res, 5000));
|
||||
resolve(false);
|
||||
})();
|
||||
});
|
||||
@ -111,9 +106,7 @@ export class KittyLogo {
|
||||
while (pngData.length > 0) {
|
||||
const dataSize = Math.min(pngData.length, chunkSize);
|
||||
|
||||
process.stdout.write(
|
||||
header + `,m=${dataSize == chunkSize ? 1 : 0};`,
|
||||
);
|
||||
process.stdout.write(header + `,m=${dataSize == chunkSize ? 1 : 0};`);
|
||||
process.stdout.write(pngData.slice(0, chunkSize));
|
||||
pngData = pngData.slice(chunkSize);
|
||||
process.stdout.write("\x1b\\");
|
||||
|
||||
@ -38,3 +38,9 @@ export function centerString(str: string, len: number): string {
|
||||
const padLeft = Math.floor(pad / 2) + str.length;
|
||||
return str.padStart(padLeft).padEnd(len);
|
||||
}
|
||||
|
||||
export function stringGlobToRegexp(str: string, flags?: string): RegExp {
|
||||
// Convert simple wildcard patterns to regex
|
||||
const escaped = str.replace(".", "\\.").replace("?", ".").replace("*", ".*");
|
||||
return new RegExp(escaped, flags);
|
||||
}
|
||||
|
||||
@ -16,19 +16,6 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare module "url" {
|
||||
interface URL {
|
||||
normalize(): string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a URL by:
|
||||
* - Removing trailing slashes (except root path)
|
||||
* - Sorting query params alphabetically
|
||||
* - Removing empty query strings
|
||||
* - Removing fragments
|
||||
*/
|
||||
export function normalizeUrl(input: string): string {
|
||||
try {
|
||||
const u = new URL(input);
|
||||
@ -52,9 +39,3 @@ export function normalizeUrl(input: string): string {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
// register extensions
|
||||
if (!URL.prototype.normalize)
|
||||
URL.prototype.normalize = function () {
|
||||
return normalizeUrl(this.toString());
|
||||
};
|
||||
@ -1,96 +1,9 @@
|
||||
import moduleAlias from "module-alias";
|
||||
moduleAlias();
|
||||
import './Array';
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import "./Array";
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
describe("Array extensions", () => {
|
||||
|
||||
it("containsAll", () => {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
assert(arr.containsAll([1, 2]));
|
||||
assert(!arr.containsAll([1, 6]));
|
||||
assert(arr.containsAll([]));
|
||||
assert([].containsAll([]));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
assert(![].containsAll([1]));
|
||||
});
|
||||
|
||||
it("partition", () => {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
const [even, odd] = arr.partition((n) => n % 2 === 0);
|
||||
assert.deepEqual(even, [2, 4]);
|
||||
assert.deepEqual(odd, [1, 3, 5]);
|
||||
});
|
||||
|
||||
it("single", () => {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
assert.strictEqual(arr.single((n) => n === 3), 3);
|
||||
assert.strictEqual(arr.single((n) => n === 6), null);
|
||||
assert.throws(() => arr.single((n) => n > 2));
|
||||
});
|
||||
|
||||
it("forEachAsync", async () => {
|
||||
const arr = [1, 2, 3];
|
||||
let sum = 0;
|
||||
await arr.forEachAsync(async (n) => {
|
||||
sum += n;
|
||||
});
|
||||
assert.strictEqual(sum, 6);
|
||||
});
|
||||
|
||||
it("remove", () => {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
arr.remove(3);
|
||||
assert.deepEqual(arr, [1, 2, 4, 5]);
|
||||
arr.remove(6);
|
||||
assert.deepEqual(arr, [1, 2, 4, 5]);
|
||||
});
|
||||
|
||||
it("first", () => {
|
||||
const arr = [1, 2, 3];
|
||||
assert.strictEqual(arr.first(), 1);
|
||||
assert.strictEqual([].first(), undefined);
|
||||
});
|
||||
|
||||
it("last", () => {
|
||||
const arr = [1, 2, 3];
|
||||
assert.strictEqual(arr.last(), 3);
|
||||
assert.strictEqual([].last(), undefined);
|
||||
});
|
||||
|
||||
it("distinct", () => {
|
||||
const arr = [1, 2, 2, 3, 3, 3];
|
||||
assert.deepEqual(arr.distinct(), [1, 2, 3]);
|
||||
assert.deepEqual([].distinct(), []);
|
||||
});
|
||||
|
||||
it("distinctBy", () => {
|
||||
const arr = [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 3 }];
|
||||
assert.deepEqual(arr.distinctBy((x) => x.id), [{ id: 1 }, { id: 2 }, { id: 3 }]);
|
||||
assert.deepEqual([].distinctBy((x) => x), []);
|
||||
});
|
||||
|
||||
it("intersect", () => {
|
||||
const arr1 = [1, 2, 3, 4];
|
||||
const arr2 = [3, 4, 5, 6];
|
||||
assert.deepEqual(arr1.intersect(arr2), [3, 4]);
|
||||
assert.deepEqual(arr1.intersect([]), []);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
assert.deepEqual([].intersect(arr2), []);
|
||||
});
|
||||
|
||||
it("except", () => {
|
||||
const arr1 = [1, 2, 3, 4];
|
||||
const arr2 = [3, 4, 5, 6];
|
||||
assert.deepEqual(arr1.except(arr2), [1, 2]);
|
||||
assert.deepEqual(arr1.except([]), [1, 2, 3, 4]);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
assert.deepEqual([].except(arr2), []);
|
||||
});
|
||||
|
||||
//
|
||||
});
|
||||
@ -18,24 +18,12 @@
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
containsAll(target: T[]): boolean;
|
||||
partition(filter: (elem: T) => boolean): [T[], T[]];
|
||||
single(filter: (elem: T) => boolean): T | null;
|
||||
forEachAsync(callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void>;
|
||||
remove(item: T): void;
|
||||
first(): T | undefined;
|
||||
last(): T | undefined;
|
||||
distinct(): T[];
|
||||
distinctBy<K>(key: (elem: T) => K): T[];
|
||||
intersect(other: T[]): T[];
|
||||
except(other: T[]): T[];
|
||||
/**
|
||||
* @deprecated never use, idk why but I can't get rid of this without errors
|
||||
*/
|
||||
remove(h: T): never;
|
||||
}
|
||||
}
|
||||
|
||||
export function arrayContainsAll<T>(arr: T[], target: T[]) {
|
||||
return target.every((v) => arr.includes(v));
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/a/50636286 */
|
||||
export function arrayPartition<T>(array: T[], filter: (elem: T) => boolean): [T[], T[]] {
|
||||
const pass: T[] = [],
|
||||
@ -44,99 +32,11 @@ export function arrayPartition<T>(array: T[], filter: (elem: T) => boolean): [T[
|
||||
return [pass, fail];
|
||||
}
|
||||
|
||||
export function arraySingle<T>(array: T[], filter: (elem: T) => boolean): T | null {
|
||||
const results = array.filter(filter);
|
||||
if (results.length > 1) throw new Error("Array contains more than one matching element");
|
||||
if (results.length === 0) return null;
|
||||
return results[0];
|
||||
}
|
||||
|
||||
export async function arrayForEachAsync<T>(array: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void> {
|
||||
await Promise.all(array.map(callback));
|
||||
}
|
||||
|
||||
export function arrayRemove<T>(this: T[], item: T): void {
|
||||
const index = this.indexOf(item);
|
||||
export function arrayRemove<T>(array: T[], item: T): void {
|
||||
const index = array.indexOf(item);
|
||||
if (index > -1) {
|
||||
this.splice(index, 1);
|
||||
array.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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function arrayIntersect<T>(this: T[], other: T[]): T[] {
|
||||
return this.filter((value) => other.includes(value));
|
||||
}
|
||||
|
||||
export function arrayExcept<T>(this: T[], other: T[]): T[] {
|
||||
return this.filter((value) => !other.includes(value));
|
||||
}
|
||||
|
||||
// register extensions
|
||||
if (!Array.prototype.containsAll)
|
||||
Array.prototype.containsAll = function <T>(this: T[], target: T[]) {
|
||||
return arrayContainsAll(this, target);
|
||||
};
|
||||
if (!Array.prototype.partition)
|
||||
Array.prototype.partition = function <T>(this: T[], filter: (elem: T) => boolean) {
|
||||
return arrayPartition(this, filter);
|
||||
};
|
||||
if (!Array.prototype.single)
|
||||
Array.prototype.single = function <T>(this: T[], filter: (elem: T) => boolean) {
|
||||
return arraySingle(this, filter);
|
||||
};
|
||||
if (!Array.prototype.forEachAsync)
|
||||
Array.prototype.forEachAsync = function <T>(this: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>) {
|
||||
return arrayForEachAsync(this, callback);
|
||||
};
|
||||
if (!Array.prototype.remove)
|
||||
Array.prototype.remove = function <T>(this: T[], item: T) {
|
||||
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));
|
||||
};
|
||||
if (!Array.prototype.intersect)
|
||||
Array.prototype.intersect = function <T>(this: T[], other: T[]) {
|
||||
return arrayIntersect.call(this, other);
|
||||
};
|
||||
if (!Array.prototype.except)
|
||||
Array.prototype.except = function <T>(this: T[], other: T[]) {
|
||||
return arrayExcept.call(this, other);
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
import moduleAlias from "module-alias";
|
||||
moduleAlias();
|
||||
import './Global';
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
describe("Global extensions", () => {
|
||||
|
||||
it("sleep", async () => {
|
||||
const start = Date.now();
|
||||
await sleep(100);
|
||||
const duration = Date.now() - start;
|
||||
assert(duration >= 100, `Sleep duration was less than expected: ${duration}ms`);
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,12 +0,0 @@
|
||||
declare global {
|
||||
function sleep(ms: number): Promise<void>;
|
||||
}
|
||||
|
||||
export function globalSleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
if (!globalThis.sleep)
|
||||
globalThis.sleep = function (ms: number): Promise<void> {
|
||||
return globalSleep(ms);
|
||||
};
|
||||
@ -1,19 +0,0 @@
|
||||
import moduleAlias from "module-alias";
|
||||
moduleAlias();
|
||||
import './Math';
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
describe("Math extensions", () => {
|
||||
|
||||
it("clamp", async () => {
|
||||
assert.strictEqual(Math.clamp(5, 1, 10), 5);
|
||||
assert.strictEqual(Math.clamp(0, 1, 10), 1);
|
||||
assert.strictEqual(Math.clamp(15, 1, 10), 10);
|
||||
assert.strictEqual(Math.clamp(-5, -10, -1), -5);
|
||||
assert.strictEqual(Math.clamp(-15, -10, -1), -10);
|
||||
assert.strictEqual(Math.clamp(-0.5, -1, 0), -0.5);
|
||||
assert.strictEqual(Math.clamp(1.5, 1, 2), 1.5);
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,31 +0,0 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2025 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare global {
|
||||
interface Math {
|
||||
clamp(value: number, min: number, max: number): number;
|
||||
}
|
||||
}
|
||||
|
||||
export function mathClamp(value: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
// register extensions
|
||||
if (!Math.clamp)
|
||||
Math.clamp = mathClamp;
|
||||
@ -1,26 +0,0 @@
|
||||
import moduleAlias from "module-alias";
|
||||
moduleAlias();
|
||||
import "./Object";
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
describe("Object extensions", () => {
|
||||
it("forEach", async () => {
|
||||
const obj: { [index:string]: number } = { a: 1, b: 2, c: 3 };
|
||||
const keys: string[] = [];
|
||||
const values: number[] = [];
|
||||
obj.forEach<number>((value, key, _) => {
|
||||
keys.push(key);
|
||||
values.push(value);
|
||||
});
|
||||
console.log(keys, values);
|
||||
assert.deepEqual(keys, ["a", "b", "c"]);
|
||||
assert.deepEqual(values, [1, 2, 3]);
|
||||
});
|
||||
|
||||
it("map", async () => {
|
||||
const obj = { a: 1, b: 2, c: 3 };
|
||||
const result = obj.map((value, key) => `${key}:${value}`);
|
||||
assert.deepEqual(result, { a: "a:1", b: "b:2", c: "c:3" });
|
||||
});
|
||||
});
|
||||
@ -1,43 +0,0 @@
|
||||
declare global {
|
||||
interface Object {
|
||||
forEach<T>(callback: (value: T, key: string, object: { [index: string]: T }) => void): void;
|
||||
map<SV, TV>(callback: (value: SV, key: string, object: { [index: string]: SV }) => TV): { [index: string]: TV };
|
||||
}
|
||||
}
|
||||
|
||||
export function objectForEach<T>(obj: { [index: string]: T }, callback: (value: T, key: string, object: { [index: string]: T }) => void): void {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
callback(obj[key], key, obj);
|
||||
});
|
||||
}
|
||||
|
||||
export function objectMap<SV, TV>(srcObj: { [index: string]: SV }, callback: (value: SV, key: string, object: { [index: string]: SV }) => TV): { [index: string]: TV } {
|
||||
if (typeof callback !== "function") throw new TypeError(`${callback} is not a function`);
|
||||
const obj: { [index: string]: TV } = {};
|
||||
Object.keys(srcObj).forEach((key) => {
|
||||
obj[key] = callback(srcObj[key], key, srcObj);
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (!Object.prototype.forEach)
|
||||
Object.defineProperty(Object.prototype, "forEach", {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
value: function (cb) {
|
||||
return objectForEach(this, cb);
|
||||
},
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
if (!Object.prototype.map)
|
||||
Object.defineProperty(Object.prototype, "map", {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
value: function (cb) {
|
||||
return objectMap(this, cb);
|
||||
},
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
});
|
||||
@ -1,15 +0,0 @@
|
||||
import moduleAlias from "module-alias";
|
||||
moduleAlias();
|
||||
import './String';
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
describe("String extensions", () => {
|
||||
|
||||
it("globToRegexp", () => {
|
||||
const pattern = "file-*.txt";
|
||||
const regex = pattern.globToRegexp();
|
||||
assert.ok(regex.test("file-123.txt"));
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,37 +0,0 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2025 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare global {
|
||||
interface String {
|
||||
globToRegexp(flags?: string): RegExp;
|
||||
}
|
||||
}
|
||||
|
||||
export function stringGlobToRegexp(str: string, flags?: string): RegExp {
|
||||
// Convert simple wildcard patterns to regex
|
||||
const escaped = str.replace(".", "\\.")
|
||||
.replace("?", ".")
|
||||
.replace("*", ".*")
|
||||
return new RegExp(escaped, flags);
|
||||
}
|
||||
|
||||
// Register extensions
|
||||
if (!String.prototype.globToRegexp)
|
||||
String.prototype.globToRegexp = function (str: string, flags?: string) {
|
||||
return stringGlobToRegexp.call(null, str, flags);
|
||||
};
|
||||
@ -1,37 +0,0 @@
|
||||
import moduleAlias from "module-alias";
|
||||
moduleAlias();
|
||||
import './Url';
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
describe("URL extensions", () => {
|
||||
|
||||
it("normalize", async () => {
|
||||
const tests: [string, string][] = [
|
||||
["http://example.com", "http://example.com/"],
|
||||
["http://example.com/", "http://example.com/"],
|
||||
["http://example.com/path/", "http://example.com/path"],
|
||||
["http://example.com/path//", "http://example.com/path/"],
|
||||
["http://example.com/path?b=2&a=1", "http://example.com/path?a=1&b=2"],
|
||||
["http://example.com/path?b=2&a=1&", "http://example.com/path?a=1&b=2"],
|
||||
["http://example.com/path?", "http://example.com/path"],
|
||||
["http://example.com/path#fragment", "http://example.com/path"],
|
||||
["http://example.com/path/?b=2&a=1#fragment", "http://example.com/path?a=1&b=2"],
|
||||
["ftp://example.com/resource/", "ftp://example.com/resource"],
|
||||
["https://example.com/resource?z=3&y=2&x=1", "https://example.com/resource?x=1&y=2&z=3"],
|
||||
["https://example.com/resource?z=3&y=2&x=1#", "https://example.com/resource?x=1&y=2&z=3"],
|
||||
["https://example.com/resource?z=3&y=2&x=1#section", "https://example.com/resource?x=1&y=2&z=3"],
|
||||
["https://example.com/resource/?z=3&y=2&x=1#section", "https://example.com/resource?x=1&y=2&z=3"],
|
||||
["https://example.com/resource//?z=3&y=2&x=1#section", "https://example.com/resource/?x=1&y=2&z=3"],
|
||||
["https://example.com/", "https://example.com/"],
|
||||
["https://example.com", "https://example.com/"],
|
||||
];
|
||||
for (const [input, expected] of tests) {
|
||||
assert.doesNotThrow(() => new URL(input), `URL("${input}") should not throw`);
|
||||
const url = new URL(input);
|
||||
const normalized = url.normalize();
|
||||
assert.strictEqual(normalized, expected, `normalize("${input}") = "${normalized}", expected "${expected}"`);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@ -1,5 +1 @@
|
||||
export * from "./Array";
|
||||
export * from "./Math";
|
||||
export * from "./Url";
|
||||
export * from "./Object";
|
||||
export * from "./String";
|
||||
@ -50,3 +50,4 @@ export * from "../../schemas/HelperTypes";
|
||||
export * from "./extensions";
|
||||
export * from "./Random";
|
||||
export * from "./WebPush";
|
||||
export * from "./Url";
|
||||
@ -2,8 +2,7 @@ import { NextFunction, Request, Response } from "express";
|
||||
import { HTTPError } from ".";
|
||||
|
||||
const OPTIONAL_PREFIX = "$";
|
||||
const EMAIL_REGEX =
|
||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
||||
export function check(schema: any) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
@ -31,11 +30,7 @@ export class Email {
|
||||
}
|
||||
}
|
||||
|
||||
export function instanceOf(
|
||||
type: any,
|
||||
value: any,
|
||||
{ path = "", optional = false }: { path?: string; optional?: boolean } = {}
|
||||
): Boolean {
|
||||
export function instanceOf(type: any, value: any, { path = "", optional = false }: { path?: string; optional?: boolean } = {}): boolean {
|
||||
if (!type) return true; // no type was specified
|
||||
|
||||
if (value == null) {
|
||||
@ -55,7 +50,9 @@ export function instanceOf(
|
||||
try {
|
||||
value = BigInt(value);
|
||||
if (typeof value === "bigint") return true;
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
//Ignore BigInt error
|
||||
}
|
||||
throw `${path} must be a bigint`;
|
||||
case Boolean:
|
||||
if (value == "true") value = true;
|
||||
@ -98,9 +95,8 @@ export function instanceOf(
|
||||
}
|
||||
if (typeof value !== "object") throw `${path} must be a object`;
|
||||
|
||||
const diff = Object.keys(value).except(
|
||||
Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x))
|
||||
);
|
||||
const filterset = new Set(Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x)));
|
||||
const diff = Object.keys(value).filter((_) => !filterset.has(_));
|
||||
|
||||
if (diff.length) throw `Unknown key ${diff}`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user