mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix: harden discord guild resolution (#6824) (thanks @gavinbmoore)
This commit is contained in:
@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Sessions/Agents: pass `agentId` through status and usage transcript-resolution paths (auto-reply, gateway usage APIs, and session cost/log loaders) so non-default agents can resolve absolute session files without path-validation failures. (#15103) Thanks @jalehman.
|
||||
- Signal/Install: auto-install `signal-cli` via Homebrew on non-x64 Linux architectures, avoiding x86_64 native binary `Exec format error` failures on arm64/arm hosts. (#15443) Thanks @jogvan-k.
|
||||
- Discord: avoid misrouting numeric guild allowlist entries to `/channels/<guildId>` by prefixing guild-only inputs with `guild:` during resolution. (#12326) Thanks @headswim.
|
||||
- Discord: ignore partial guild entries when resolving invite allowlists and directory lookups to avoid crashes on missing `id`/`name` data. (#6824) Thanks @gavinbmoore.
|
||||
- Config: preserve `${VAR}` env references when writing config files so `openclaw config set/apply/patch` does not persist secrets to disk. Thanks @thewilloftheshadow.
|
||||
- Process/Exec: avoid shell execution for `.exe` commands on Windows so env overrides work reliably in `runCommandWithTimeout`. Thanks @thewilloftheshadow.
|
||||
- Web tools/web_fetch: prefer `text/markdown` responses for Cloudflare Markdown for Agents, add `cf-markdown` extraction for markdown bodies, and redact fetched URLs in `x-markdown-tokens` debug logs to avoid leaking raw paths/query params. (#15376) Thanks @Yaxuan42.
|
||||
|
||||
@@ -5,7 +5,12 @@ import { fetchDiscord } from "./api.js";
|
||||
import { normalizeDiscordSlug } from "./monitor/allow-list.js";
|
||||
import { normalizeDiscordToken } from "./token.js";
|
||||
|
||||
type DiscordGuild = { id: string; name: string };
|
||||
type DiscordGuild = { id?: string; name?: string };
|
||||
|
||||
function isDiscordGuildWithId(guild: DiscordGuild): guild is { id: string; name?: string } {
|
||||
return typeof guild.id === "string";
|
||||
}
|
||||
|
||||
type DiscordUser = { id: string; username: string; global_name?: string; bot?: boolean };
|
||||
type DiscordMember = { user: DiscordUser; nick?: string | null };
|
||||
type DiscordChannel = { id: string; name?: string | null };
|
||||
@@ -28,7 +33,7 @@ export async function listDiscordDirectoryGroupsLive(
|
||||
}
|
||||
const query = normalizeQuery(params.query);
|
||||
const rawGuilds = await fetchDiscord<DiscordGuild[]>("/users/@me/guilds", token);
|
||||
const guilds = rawGuilds.filter((g) => g.id && g.name);
|
||||
const guilds = rawGuilds.filter(isDiscordGuildWithId);
|
||||
const rows: ChannelDirectoryEntry[] = [];
|
||||
|
||||
for (const guild of guilds) {
|
||||
@@ -71,7 +76,7 @@ export async function listDiscordDirectoryPeersLive(
|
||||
}
|
||||
|
||||
const rawGuilds = await fetchDiscord<DiscordGuild[]>("/users/@me/guilds", token);
|
||||
const guilds = rawGuilds.filter((g) => g.id && g.name);
|
||||
const guilds = rawGuilds.filter(isDiscordGuildWithId);
|
||||
const rows: ChannelDirectoryEntry[] = [];
|
||||
const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : 25;
|
||||
|
||||
|
||||
@@ -31,6 +31,32 @@ describe("resolveDiscordChannelAllowlist", () => {
|
||||
expect(res[0]?.channelId).toBe("c1");
|
||||
});
|
||||
|
||||
it("ignores partial guild entries", async () => {
|
||||
const fetcher = async (url: string) => {
|
||||
if (url.endsWith("/users/@me/guilds")) {
|
||||
return jsonResponse([
|
||||
{ id: "g1", name: "Guild One" },
|
||||
{ id: "g2" },
|
||||
{ name: "Missing ID" },
|
||||
]);
|
||||
}
|
||||
if (url.endsWith("/guilds/g1/channels")) {
|
||||
return jsonResponse([{ id: "c1", name: "general", guild_id: "g1", type: 0 }]);
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
};
|
||||
|
||||
const res = await resolveDiscordChannelAllowlist({
|
||||
token: "test",
|
||||
entries: ["Guild One/general"],
|
||||
fetcher,
|
||||
});
|
||||
|
||||
expect(res[0]?.resolved).toBe(true);
|
||||
expect(res[0]?.guildId).toBe("g1");
|
||||
expect(res[0]?.channelId).toBe("c1");
|
||||
});
|
||||
|
||||
it("resolves channel id to guild", async () => {
|
||||
const fetcher = async (url: string) => {
|
||||
if (url.endsWith("/users/@me/guilds")) {
|
||||
|
||||
41
src/discord/resolve-users.test.ts
Normal file
41
src/discord/resolve-users.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveDiscordUserAllowlist } from "./resolve-users.js";
|
||||
|
||||
function jsonResponse(body: unknown) {
|
||||
return new Response(JSON.stringify(body), { status: 200 });
|
||||
}
|
||||
|
||||
describe("resolveDiscordUserAllowlist", () => {
|
||||
it("ignores partial guild entries", async () => {
|
||||
const fetcher = async (url: string) => {
|
||||
if (url.endsWith("/users/@me/guilds")) {
|
||||
return jsonResponse([
|
||||
{ id: "g1", name: "Guild One" },
|
||||
{ id: "g2" },
|
||||
{ name: "Missing ID" },
|
||||
]);
|
||||
}
|
||||
if (url.includes("/guilds/g1/members/search")) {
|
||||
return jsonResponse([
|
||||
{
|
||||
user: {
|
||||
id: "u1",
|
||||
username: "alex",
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
};
|
||||
|
||||
const res = await resolveDiscordUserAllowlist({
|
||||
token: "test",
|
||||
entries: ["Guild One/alex"],
|
||||
fetcher,
|
||||
});
|
||||
|
||||
expect(res[0]?.resolved).toBe(true);
|
||||
expect(res[0]?.id).toBe("u1");
|
||||
expect(res[0]?.guildId).toBe("g1");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user