Mention inbox, hopefully

This commit is contained in:
Rory&
2025-11-26 15:27:19 +01:00
parent 0a649a0de2
commit 06e3308355
6 changed files with 223 additions and 45 deletions

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/61e66448-285a-4205-a1d7-c2704d5afb2c/console.sql" value="61e66448-285a-4205-a1d7-c2704d5afb2c" />
</component>
</project>

95
.idea/workspace.xml generated
View File

@@ -43,46 +43,46 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;NIXITCH_NIXPKGS_CONFIG&quot;: &quot;/etc/nix/nixpkgs-config.nix&quot;,
&quot;NIXITCH_NIX_CONF_DIR&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_OTHER_STORES&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_PATH&quot;: &quot;/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source&quot;,
&quot;NIXITCH_NIX_PROFILES&quot;: &quot;/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile&quot;,
&quot;NIXITCH_NIX_REMOTE&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_USER_PROFILE_DIR&quot;: &quot;/nix/var/nix/profiles/per-user/Rory&quot;,
&quot;Node.js.Server.ts.executor&quot;: &quot;Debug&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;javascript.nodejs.core.library.configured.version&quot;: &quot;24.8.0&quot;,
&quot;javascript.nodejs.core.library.typings.version&quot;: &quot;24.7.0&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/Rory/git/spacebar/server-master/src/util/migration/postgres&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_interpreter_path&quot;: &quot;node&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;npm.Start API.executor&quot;: &quot;Run&quot;,
&quot;npm.Start CDN.executor&quot;: &quot;Run&quot;,
&quot;npm.Start Gateway.executor&quot;: &quot;Run&quot;,
&quot;npm.build.executor&quot;: &quot;Run&quot;,
&quot;npm.start.executor&quot;: &quot;Debug&quot;,
&quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/prettier&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;ts.external.directory.path&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/typescript/lib&quot;
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix",
"NIXITCH_NIX_CONF_DIR": "",
"NIXITCH_NIX_OTHER_STORES": "",
"NIXITCH_NIX_PATH": "/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source",
"NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile",
"NIXITCH_NIX_REMOTE": "",
"NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/Rory",
"Node.js.Server.ts.executor": "Debug",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"javascript.nodejs.core.library.configured.version": "24.8.0",
"javascript.nodejs.core.library.typings.version": "24.7.0",
"last_opened_file_path": "/home/Rory/git/spacebar/server-master/src/util/migration/postgres",
"node.js.detected.package.eslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_interpreter_path": "node",
"nodejs_package_manager_path": "npm",
"npm.Start API.executor": "Run",
"npm.Start CDN.executor": "Run",
"npm.Start Gateway.executor": "Run",
"npm.build.executor": "Run",
"npm.start.executor": "Debug",
"prettierjs.PrettierConfiguration.Package": "/home/Rory/git/spacebar/server-master/node_modules/prettier",
"settings.editor.selected.configurable": "settings.javascript.linters.tslint",
"ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib"
},
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
"keyToStringList": {
"DatabaseDriversLRU": [
"postgresql"
],
&quot;GitStage.ChangesTree.GroupingKeys&quot;: [
&quot;directory&quot;,
&quot;module&quot;,
&quot;repository&quot;
"GitStage.ChangesTree.GroupingKeys": [
"directory",
"module",
"repository"
]
}
}</component>
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/util/migration/postgres" />
@@ -111,14 +111,24 @@
<value>
<map>
<entry key="Start API" value="STOPPED" />
<entry key="Start CDN" value="STOPPED" />
<entry key="Start Gateway" value="STOPPED" />
<entry key="build" value="STOPPED" />
<entry key="Start bundle" value="STOPPED" />
</map>
</value>
</entry>
</map>
</option>
</component>
<component name="RunManager" selected="Compound.Start separated">
<list>
<item itemvalue="Compound.Start separated" />
<item itemvalue="npm.Start API" />
<item itemvalue="npm.Start CDN" />
<item itemvalue="npm.Start Gateway" />
<item itemvalue="npm.Start bundle" />
</list>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
@@ -155,6 +165,17 @@
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>

Binary file not shown.

View File

@@ -24,6 +24,7 @@ const {
NO_AUTHORIZATION_ROUTES,
} = require("../dist/api/middlewares/Authentication");
require("../dist/util/util/extensions");
const { bgRedBright } = require("picocolors");
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
@@ -84,7 +85,7 @@ function combineSchemas(schemas) {
for (const key in definitions) {
if (!schemaRegEx.test(key)) {
console.error(`Invalid schema name: ${key}, context:`, definitions[key]);
console.error(`${bgRedBright("ERROR")} Invalid schema name: ${key}, context:`, definitions[key]);
continue;
}
specification.components = specification.components || {};

View File

@@ -2,6 +2,7 @@ const express = require("express");
const path = require("path");
const { traverseDirectory } = require("lambert-server");
const RouteUtility = require("../../dist/api/util/handlers/route.js");
const { bgRedBright } = require("picocolors");
const methods = ["get", "post", "put", "delete", "patch"];
const routes = new Map();
@@ -24,7 +25,7 @@ function proxy(file, method, prefix, path, ...args) {
const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true);
if (!opts)
return console.error(
`${file} has route without route() description middleware`,
`${bgRedBright("ERROR")} ${file} has route without route() description middleware`,
);
console.log(`${method.toUpperCase().padStart("OPTIONS".length)} ${prefix + path}`);

View File

@@ -0,0 +1,161 @@
/*
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/>.
*/
import { route } from "@spacebar/api";
import { Snowflake, User, Message, Member, Channel, Permissions, timePromise } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { In } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.get(
"",
route({
responses: {
200: {
body: "MessageListResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
// AFAICT this endpoint doesn't list DMs
async (req: Request, res: Response) => {
const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 50;
const everyone = !!req.query.everyone;
const roles = !!req.query.roles;
const user = await User.findOneOrFail({
where: { id: req.user_id },
});
const memberships = await Member.find({
where: { id: req.user_id },
select: {
guild_id: true,
id: true,
communication_disabled_until: true,
roles: {
// We don't want to include all guild roles, as this could cause a lot more explosive behavior
id: true,
position: true,
permissions: true,
mentionable: true, // cause we can skip querying for unmentionable roles
},
guild: {
id: true,
owner_id: true,
},
},
relations: ["guild", "roles"],
});
const channels = await Channel.find({
where: {
guild_id: In(memberships.map((m) => m.guild_id)),
},
select: { id: true, permission_overwrites: true },
});
const visibleChannels = channels.filter((c) => {
const member = memberships.find((m) => m.guild_id === c.guild_id)!;
return Permissions.finalPermission({
user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 },
guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.guild.roles },
channel: c,
}).has("VIEW_CHANNEL");
});
const visibleChannelIds = visibleChannels.map((c) => c.id);
const ownedMentionableRoleIds = memberships.reduce((acc, m) => {
acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id));
return acc;
}, [] as Snowflake[]);
const [
{ result: userMentions, elapsed: userMentionQueryTime },
{ result: roleMentions, elapsed: roleMentionQueryTime },
{ result: everyoneMentions, elapsed: everyoneMentionQueryTime },
] = await Promise.all([
await timePromise(() =>
Message.find({
where: {
channel_id: In(visibleChannelIds),
mentions: { id: user.id },
},
select: {
id: true,
timestamp: true,
},
order: {
timestamp: "DESC",
},
take: limit ? Number(limit) : 50,
}),
),
await timePromise(() =>
!roles
? Promise.resolve([])
: Message.find({
where: {
channel_id: In(visibleChannelIds),
mention_roles: { id: In(ownedMentionableRoleIds) },
},
select: {
id: true,
timestamp: true,
},
order: {
timestamp: "DESC",
},
take: limit ? Number(limit) : 50,
}),
),
await timePromise(() =>
!everyone
? Promise.resolve([])
: Message.find({
where: {
channel_id: In(visibleChannelIds),
mention_everyone: true,
},
select: {
id: true,
timestamp: true,
},
order: {
timestamp: "DESC",
},
take: limit ? Number(limit) : 50,
}),
),
]);
const allMentions = [...userMentions, ...roleMentions, ...everyoneMentions];
console.log(`[Inbox/mentions] User ${user.id} query results: totalRecs=${allMentions.length} | user=${userMentions.length} (took ${userMentionQueryTime}ms), role=${roleMentions.length} (took ${roleMentionQueryTime}ms), everyone=${everyoneMentions.length} (took ${everyoneMentionQueryTime}ms)`);
return res.json(
allMentions
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
.distinctBy((m) => m.id)
.slice(0, limit),
);
},
);
export default router;