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": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.936.0",
|
"@aws-sdk/client-s3": "^3.936.0",
|
||||||
|
"@spacebarchat/medooze-webrtc": "^1.0.8",
|
||||||
"@toondepauw/node-zstd": "^1.2.0",
|
"@toondepauw/node-zstd": "^1.2.0",
|
||||||
"@types/web-push": "^3.6.4",
|
"@types/web-push": "^3.6.4",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
|
|||||||
@ -121,7 +121,7 @@ function apiRoutes(missingRoutes) {
|
|||||||
const tags = Array.from(routes.keys())
|
const tags = Array.from(routes.keys())
|
||||||
.map((x) => getTag(x))
|
.map((x) => getTag(x))
|
||||||
.sort((a, b) => a.localeCompare(b));
|
.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) => {
|
routes.forEach((route, pathAndMethod) => {
|
||||||
const [p, method] = pathAndMethod.split("|");
|
const [p, method] = pathAndMethod.split("|");
|
||||||
@ -213,7 +213,7 @@ function apiRoutes(missingRoutes) {
|
|||||||
obj.parameters = [...(obj.parameters || []), ...query];
|
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(/\/$/, ""))) {
|
if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
|
||||||
obj["x-badges"] = [
|
obj["x-badges"] = [
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
|
arrayRemove,
|
||||||
Channel,
|
Channel,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
Emoji,
|
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);
|
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);
|
if (!already_added) throw new HTTPError("Reaction not found", 404);
|
||||||
message.reactions.remove(already_added);
|
arrayRemove(message.reactions, already_added);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
message.save(),
|
message.save(),
|
||||||
@ -283,7 +284,7 @@ router.delete(
|
|||||||
|
|
||||||
already_added.count--;
|
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);
|
else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
|
||||||
|
|
||||||
await message.save();
|
await message.save();
|
||||||
@ -340,7 +341,7 @@ router.delete(
|
|||||||
|
|
||||||
already_added.count--;
|
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);
|
else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
|
||||||
|
|
||||||
await message.save();
|
await message.save();
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import {
|
|||||||
Relationship,
|
Relationship,
|
||||||
Rights,
|
Rights,
|
||||||
Snowflake,
|
Snowflake,
|
||||||
|
stringGlobToRegexp,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
User,
|
User,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
@ -244,24 +245,28 @@ router.get(
|
|||||||
return x;
|
return x;
|
||||||
});
|
});
|
||||||
|
|
||||||
await ret
|
await Promise.all(
|
||||||
.filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user)
|
ret
|
||||||
.forEachAsync(async (x: MessageCreateSchema) => {
|
.filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user)
|
||||||
x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } });
|
.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
|
// polyfill message references for old messages
|
||||||
await ret
|
await Promise.all(
|
||||||
.filter((msg) => msg.message_reference && !msg.referenced_message?.id)
|
ret
|
||||||
.forEachAsync(async (msg) => {
|
.filter((msg) => msg.message_reference && !msg.referenced_message?.id)
|
||||||
const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
|
.map(async (msg) => {
|
||||||
id: msg.message_reference!.message_id,
|
const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
|
||||||
};
|
id: msg.message_reference!.message_id,
|
||||||
if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id;
|
};
|
||||||
if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id;
|
if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id;
|
||||||
|
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"] });
|
msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] });
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return res.json(ret);
|
return res.json(ret);
|
||||||
},
|
},
|
||||||
@ -449,8 +454,8 @@ router.post(
|
|||||||
|
|
||||||
if (rule.trigger_type == AutomodTriggerTypes.CUSTOM_WORDS) {
|
if (rule.trigger_type == AutomodTriggerTypes.CUSTOM_WORDS) {
|
||||||
const triggerMeta = rule.trigger_metadata as AutomodCustomWordsRule;
|
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 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) => k.globToRegexp("i"));
|
const allowedRegexes = triggerMeta.allow_list.map((k) => stringGlobToRegexp(k, "i"));
|
||||||
|
|
||||||
const matches = regexes
|
const matches = regexes
|
||||||
.map((r) => message.content!.match(r))
|
.map((r) => message.content!.match(r))
|
||||||
|
|||||||
@ -47,10 +47,7 @@ router.put(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (channel.type !== ChannelType.GROUP_DM) {
|
if (channel.type !== ChannelType.GROUP_DM) {
|
||||||
const recipients = [
|
const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])];
|
||||||
...(channel.recipients?.map((r) => r.user_id) || []),
|
|
||||||
user_id,
|
|
||||||
].distinct();
|
|
||||||
|
|
||||||
const new_channel = await Channel.createDMChannel(
|
const new_channel = await Channel.createDMChannel(
|
||||||
recipients,
|
recipients,
|
||||||
|
|||||||
@ -40,11 +40,13 @@ router.get(
|
|||||||
relations: PublicInviteRelation,
|
relations: PublicInviteRelation,
|
||||||
});
|
});
|
||||||
|
|
||||||
await invites
|
await Promise.all(
|
||||||
.filter((i) => i.isExpired())
|
invites
|
||||||
.forEachAsync(async (i) => {
|
.filter((i) => i.isExpired())
|
||||||
await Invite.delete({ code: i.code });
|
.map(async (i) => {
|
||||||
});
|
await Invite.delete({ code: i.code });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return res.json(invites.filter((i) => !i.isExpired()));
|
return res.json(invites.filter((i) => !i.isExpired()));
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import { DiscordApiErrors, Member } from "@spacebar/util";
|
import { DiscordApiErrors, Member, arrayPartition } from "@spacebar/util";
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
|
|
||||||
const router = Router({ mergeParams: true });
|
const router = Router({ mergeParams: true });
|
||||||
@ -38,11 +38,7 @@ router.patch(
|
|||||||
relations: ["roles"],
|
relations: ["roles"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const [add, remove] = members.partition(
|
const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id));
|
||||||
(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
|
// TODO (erkin): have a bulk add/remove function that adds the roles in a single txn
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|||||||
@ -42,7 +42,7 @@ router.get(
|
|||||||
await Message.find({
|
await Message.find({
|
||||||
where: { channel_id: channel?.id },
|
where: { channel_id: channel?.id },
|
||||||
order: { timestamp: "DESC" },
|
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[];
|
).filter((x) => x !== null) as Message[];
|
||||||
|
|
||||||
|
|||||||
@ -67,10 +67,8 @@ router.patch(
|
|||||||
relations: ["settings"],
|
relations: ["settings"],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user.settings)
|
if (!user.settings) user.settings = UserSettings.create<UserSettings>(body);
|
||||||
user.settings = UserSettings.create(body as UserSettingsUpdateSchema);
|
else user.settings.assign(body);
|
||||||
else
|
|
||||||
user.settings.assign(body);
|
|
||||||
|
|
||||||
if (body.guild_folders)
|
if (body.guild_folders)
|
||||||
user.settings.guild_folders = 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,
|
user_id: this.user_id,
|
||||||
data: sessions,
|
data: sessions,
|
||||||
} as SessionsReplace);
|
} as SessionsReplace);
|
||||||
const session = sessions.first() || {
|
const session = sessions[0] || {
|
||||||
activities: [],
|
activities: [],
|
||||||
client_status: {},
|
client_status: {},
|
||||||
status: "offline",
|
status: "offline",
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import {
|
|||||||
Presence,
|
Presence,
|
||||||
Channel,
|
Channel,
|
||||||
Permissions,
|
Permissions,
|
||||||
|
arrayPartition,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import {
|
import {
|
||||||
WebSocket,
|
WebSocket,
|
||||||
@ -51,16 +52,13 @@ const getMostRelevantSession = (sessions: Session[]) => {
|
|||||||
invisible: 3,
|
invisible: 3,
|
||||||
offline: 4,
|
offline: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
// sort sessions by relevance
|
// sort sessions by relevance
|
||||||
sessions = sessions.sort((a, b) => {
|
sessions = sessions.sort((a, b) => {
|
||||||
return (
|
return statusMap[a.status] - statusMap[b.status] + ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2;
|
||||||
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]) {
|
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 groups = [];
|
||||||
const items = [];
|
const items = [];
|
||||||
const member_roles = members
|
const member_roles = [
|
||||||
.map((m) => m.roles)
|
...new Map(
|
||||||
.flat()
|
members
|
||||||
.distinctBy((r: Role) => r.id);
|
.map((m) => m.roles)
|
||||||
member_roles.push(
|
.flat()
|
||||||
member_roles.splice(
|
.map((role) => [role.id, role] as [string, Role]),
|
||||||
member_roles.findIndex((x) => x.id === x.guild_id),
|
).values(),
|
||||||
1,
|
];
|
||||||
)[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
const offlineItems = [];
|
const offlineItems = [];
|
||||||
|
|
||||||
for (const role of member_roles) {
|
for (const role of member_roles) {
|
||||||
const [role_members, other_members] = members.partition(
|
const [role_members, other_members] = arrayPartition(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 = {
|
||||||
count: role_members.length,
|
count: role_members.length,
|
||||||
id: role.id === guild_id ? "online" : role.id,
|
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");
|
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;
|
if (!channel_id) return;
|
||||||
|
|
||||||
const permissions = await getPermission(this.user_id, guild_id, channel_id);
|
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
|
const groups = [...new Set(ops.map((x) => x.groups).flat())];
|
||||||
.map((x) => x.groups)
|
|
||||||
.flat()
|
|
||||||
.distinct();
|
|
||||||
|
|
||||||
await Send(this, {
|
await Send(this, {
|
||||||
op: OPCODES.Dispatch,
|
op: OPCODES.Dispatch,
|
||||||
|
|||||||
@ -255,7 +255,7 @@ export class Channel extends BaseClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
|
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
|
// 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
|
||||||
|
|
||||||
@ -280,7 +280,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 (re.containsAll(channelRecipients)) {
|
if (channelRecipients.every((_) => re.includes(_))) {
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
channel = ur.channel;
|
channel = ur.channel;
|
||||||
await ur.assign({ closed: false }).save();
|
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));
|
userPerms = new Permissions(userPerms.remove(overwrite.deny).add(overwrite.allow));
|
||||||
|
|
||||||
// member overwrite, throws if somehow we have multiple overwrites for the same member
|
// 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));
|
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 { User } from "./User";
|
||||||
import { VoiceState } from "./VoiceState";
|
import { VoiceState } from "./VoiceState";
|
||||||
import { Webhook } from "./Webhook";
|
import { Webhook } from "./Webhook";
|
||||||
|
import { arrayRemove } from "@spacebar/util";
|
||||||
|
|
||||||
// TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0}
|
// TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0}
|
||||||
// TODO: guild_scheduled_events
|
// TODO: guild_scheduled_events
|
||||||
@ -420,7 +421,7 @@ export class Guild extends BaseClass {
|
|||||||
if (typeof insertPoint == "string") position = guild.channel_ordering.indexOf(insertPoint) + 1;
|
if (typeof insertPoint == "string") position = guild.channel_ordering.indexOf(insertPoint) + 1;
|
||||||
else position = insertPoint;
|
else position = insertPoint;
|
||||||
|
|
||||||
guild.channel_ordering.remove(channel_id);
|
arrayRemove(guild.channel_ordering, channel_id);
|
||||||
|
|
||||||
guild.channel_ordering.splice(position, 0, channel_id);
|
guild.channel_ordering.splice(position, 0, channel_id);
|
||||||
await Guild.update({ id: guild_id }, { channel_ordering: guild.channel_ordering });
|
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) {
|
for (const channel of qry) {
|
||||||
console.warn(JSON.stringify(channel));
|
console.warn(JSON.stringify(channel));
|
||||||
}
|
}
|
||||||
|
throw new Error("Array contains more than one matching element");
|
||||||
}
|
}
|
||||||
|
|
||||||
// throw if multiple
|
return qry[0];
|
||||||
return qry.single((_) => true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
|
|||||||
return new FieldError(
|
return new FieldError(
|
||||||
50035,
|
50035,
|
||||||
"Invalid Form Body",
|
"Invalid Form Body",
|
||||||
fields.map<ErrorContent, ObjectErrorContent>(({ message, code }) => ({
|
Object.values(fields).map(({ message, code }) => ({
|
||||||
_errors: [
|
_errors: [
|
||||||
{
|
{
|
||||||
message,
|
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 code: string | number,
|
||||||
public message: string,
|
public message: string,
|
||||||
public errors?: object, // TODO: I don't like this typing.
|
public errors?: object, // TODO: I don't like this typing.
|
||||||
public _ajvErrors?: ErrorObject[]
|
public _ajvErrors?: ErrorObject[],
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,12 +15,9 @@ export class KittyLogo {
|
|||||||
public static async initialise() {
|
public static async initialise() {
|
||||||
this.isSupported = await this.checkSupport();
|
this.isSupported = await this.checkSupport();
|
||||||
if (this.isSupported)
|
if (this.isSupported)
|
||||||
this.iconCache = readFileSync(
|
this.iconCache = readFileSync(__dirname + "/../../../assets/icon.png", {
|
||||||
__dirname + "/../../../assets/icon.png",
|
encoding: "base64",
|
||||||
{
|
});
|
||||||
encoding: "base64",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static printLogo(): void {
|
public static printLogo(): void {
|
||||||
@ -77,11 +74,9 @@ export class KittyLogo {
|
|||||||
if (resp.startsWith("\x1B_Gi=31;OK")) resolve(true);
|
if (resp.startsWith("\x1B_Gi=31;OK")) resolve(true);
|
||||||
else resolve(false);
|
else resolve(false);
|
||||||
});
|
});
|
||||||
process.stdout.write(
|
process.stdout.write("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c");
|
||||||
"\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);
|
resolve(false);
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
@ -111,9 +106,7 @@ export class KittyLogo {
|
|||||||
while (pngData.length > 0) {
|
while (pngData.length > 0) {
|
||||||
const dataSize = Math.min(pngData.length, chunkSize);
|
const dataSize = Math.min(pngData.length, chunkSize);
|
||||||
|
|
||||||
process.stdout.write(
|
process.stdout.write(header + `,m=${dataSize == chunkSize ? 1 : 0};`);
|
||||||
header + `,m=${dataSize == chunkSize ? 1 : 0};`,
|
|
||||||
);
|
|
||||||
process.stdout.write(pngData.slice(0, chunkSize));
|
process.stdout.write(pngData.slice(0, chunkSize));
|
||||||
pngData = pngData.slice(chunkSize);
|
pngData = pngData.slice(chunkSize);
|
||||||
process.stdout.write("\x1b\\");
|
process.stdout.write("\x1b\\");
|
||||||
|
|||||||
@ -37,4 +37,10 @@ export function centerString(str: string, len: number): string {
|
|||||||
const pad = len - str.length;
|
const pad = len - str.length;
|
||||||
const padLeft = Math.floor(pad / 2) + str.length;
|
const padLeft = Math.floor(pad / 2) + str.length;
|
||||||
return str.padStart(padLeft).padEnd(len);
|
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/>.
|
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 {
|
export function normalizeUrl(input: string): string {
|
||||||
try {
|
try {
|
||||||
const u = new URL(input);
|
const u = new URL(input);
|
||||||
@ -52,9 +39,3 @@ export function normalizeUrl(input: string): string {
|
|||||||
return input;
|
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";
|
import moduleAlias from "module-alias";
|
||||||
moduleAlias();
|
moduleAlias();
|
||||||
import './Array';
|
import "./Array";
|
||||||
import { describe, it } from 'node:test';
|
import { describe, it } from "node:test";
|
||||||
import assert from 'node:assert/strict';
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
describe("Array extensions", () => {
|
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 {
|
declare global {
|
||||||
interface Array<T> {
|
interface Array<T> {
|
||||||
containsAll(target: T[]): boolean;
|
/**
|
||||||
partition(filter: (elem: T) => boolean): [T[], T[]];
|
* @deprecated never use, idk why but I can't get rid of this without errors
|
||||||
single(filter: (elem: T) => boolean): T | null;
|
*/
|
||||||
forEachAsync(callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void>;
|
remove(h: T): never;
|
||||||
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[];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function arrayContainsAll<T>(arr: T[], target: T[]) {
|
|
||||||
return target.every((v) => arr.includes(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* https://stackoverflow.com/a/50636286 */
|
/* https://stackoverflow.com/a/50636286 */
|
||||||
export function arrayPartition<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[] = [],
|
||||||
@ -44,99 +32,11 @@ export function arrayPartition<T>(array: T[], filter: (elem: T) => boolean): [T[
|
|||||||
return [pass, fail];
|
return [pass, fail];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function arraySingle<T>(array: T[], filter: (elem: T) => boolean): T | null {
|
export function arrayRemove<T>(array: T[], item: T): void {
|
||||||
const results = array.filter(filter);
|
const index = array.indexOf(item);
|
||||||
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);
|
|
||||||
if (index > -1) {
|
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
|
// 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 "./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 "./extensions";
|
||||||
export * from "./Random";
|
export * from "./Random";
|
||||||
export * from "./WebPush";
|
export * from "./WebPush";
|
||||||
|
export * from "./Url";
|
||||||
@ -2,8 +2,7 @@ import { NextFunction, Request, Response } from "express";
|
|||||||
import { HTTPError } from ".";
|
import { HTTPError } from ".";
|
||||||
|
|
||||||
const OPTIONAL_PREFIX = "$";
|
const OPTIONAL_PREFIX = "$";
|
||||||
const EMAIL_REGEX =
|
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,}))$/;
|
||||||
/^(([^<>()\[\]\\.,;:\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) {
|
export function check(schema: any) {
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
@ -31,11 +30,7 @@ export class Email {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function instanceOf(
|
export function instanceOf(type: any, value: any, { path = "", optional = false }: { path?: string; optional?: boolean } = {}): boolean {
|
||||||
type: any,
|
|
||||||
value: any,
|
|
||||||
{ path = "", optional = false }: { path?: string; optional?: boolean } = {}
|
|
||||||
): Boolean {
|
|
||||||
if (!type) return true; // no type was specified
|
if (!type) return true; // no type was specified
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@ -55,7 +50,9 @@ export function instanceOf(
|
|||||||
try {
|
try {
|
||||||
value = BigInt(value);
|
value = BigInt(value);
|
||||||
if (typeof value === "bigint") return true;
|
if (typeof value === "bigint") return true;
|
||||||
} catch (error) {}
|
} catch (error) {
|
||||||
|
//Ignore BigInt error
|
||||||
|
}
|
||||||
throw `${path} must be a bigint`;
|
throw `${path} must be a bigint`;
|
||||||
case Boolean:
|
case Boolean:
|
||||||
if (value == "true") value = true;
|
if (value == "true") value = true;
|
||||||
@ -98,9 +95,8 @@ export function instanceOf(
|
|||||||
}
|
}
|
||||||
if (typeof value !== "object") throw `${path} must be a object`;
|
if (typeof value !== "object") throw `${path} must be a object`;
|
||||||
|
|
||||||
const diff = Object.keys(value).except(
|
const filterset = new Set(Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x)));
|
||||||
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}`;
|
if (diff.length) throw `Unknown key ${diff}`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user