mirror of https://github.com/openclaw/openclaw.git
231 lines
5.9 KiB
TypeScript
231 lines
5.9 KiB
TypeScript
import { resolveMatrixAuth } from "./matrix/client.js";
|
|
import { MatrixAuthedHttpClient } from "./matrix/sdk/http-client.js";
|
|
import { isMatrixQualifiedUserId, normalizeMatrixMessagingTarget } from "./matrix/target-ids.js";
|
|
import type { ChannelDirectoryEntry } from "./runtime-api.js";
|
|
|
|
type MatrixUserResult = {
|
|
user_id?: string;
|
|
display_name?: string;
|
|
};
|
|
|
|
type MatrixUserDirectoryResponse = {
|
|
results?: MatrixUserResult[];
|
|
};
|
|
|
|
type MatrixJoinedRoomsResponse = {
|
|
joined_rooms?: string[];
|
|
};
|
|
|
|
type MatrixRoomNameState = {
|
|
name?: string;
|
|
};
|
|
|
|
type MatrixAliasLookup = {
|
|
room_id?: string;
|
|
};
|
|
|
|
type MatrixDirectoryLiveParams = {
|
|
cfg: unknown;
|
|
accountId?: string | null;
|
|
query?: string | null;
|
|
limit?: number | null;
|
|
};
|
|
|
|
type MatrixResolvedAuth = Awaited<ReturnType<typeof resolveMatrixAuth>>;
|
|
|
|
const MATRIX_DIRECTORY_TIMEOUT_MS = 10_000;
|
|
|
|
function normalizeQuery(value?: string | null): string {
|
|
return value?.trim() ?? "";
|
|
}
|
|
|
|
function resolveMatrixDirectoryLimit(limit?: number | null): number {
|
|
return typeof limit === "number" && Number.isFinite(limit) && limit > 0
|
|
? Math.max(1, Math.floor(limit))
|
|
: 20;
|
|
}
|
|
|
|
function createMatrixDirectoryClient(auth: MatrixResolvedAuth): MatrixAuthedHttpClient {
|
|
return new MatrixAuthedHttpClient(auth.homeserver, auth.accessToken, auth.ssrfPolicy);
|
|
}
|
|
|
|
async function resolveMatrixDirectoryContext(params: MatrixDirectoryLiveParams): Promise<{
|
|
auth: MatrixResolvedAuth;
|
|
client: MatrixAuthedHttpClient;
|
|
query: string;
|
|
queryLower: string;
|
|
} | null> {
|
|
const query = normalizeQuery(params.query);
|
|
if (!query) {
|
|
return null;
|
|
}
|
|
const auth = await resolveMatrixAuth({ cfg: params.cfg as never, accountId: params.accountId });
|
|
return {
|
|
auth,
|
|
client: createMatrixDirectoryClient(auth),
|
|
query,
|
|
queryLower: query.toLowerCase(),
|
|
};
|
|
}
|
|
|
|
function createGroupDirectoryEntry(params: {
|
|
id: string;
|
|
name: string;
|
|
handle?: string;
|
|
}): ChannelDirectoryEntry {
|
|
return {
|
|
kind: "group",
|
|
id: params.id,
|
|
name: params.name,
|
|
handle: params.handle,
|
|
} satisfies ChannelDirectoryEntry;
|
|
}
|
|
|
|
async function requestMatrixJson<T>(
|
|
client: MatrixAuthedHttpClient,
|
|
params: {
|
|
method: "GET" | "POST";
|
|
endpoint: string;
|
|
body?: unknown;
|
|
},
|
|
): Promise<T> {
|
|
return (await client.requestJson({
|
|
method: params.method,
|
|
endpoint: params.endpoint,
|
|
body: params.body,
|
|
timeoutMs: MATRIX_DIRECTORY_TIMEOUT_MS,
|
|
})) as T;
|
|
}
|
|
|
|
export async function listMatrixDirectoryPeersLive(
|
|
params: MatrixDirectoryLiveParams,
|
|
): Promise<ChannelDirectoryEntry[]> {
|
|
const query = normalizeQuery(params.query);
|
|
if (!query) {
|
|
return [];
|
|
}
|
|
const directUserId = normalizeMatrixMessagingTarget(query);
|
|
if (directUserId && isMatrixQualifiedUserId(directUserId)) {
|
|
return [{ kind: "user", id: directUserId }];
|
|
}
|
|
const context = await resolveMatrixDirectoryContext({
|
|
...params,
|
|
query,
|
|
});
|
|
if (!context) {
|
|
return [];
|
|
}
|
|
|
|
const res = await requestMatrixJson<MatrixUserDirectoryResponse>(context.client, {
|
|
method: "POST",
|
|
endpoint: "/_matrix/client/v3/user_directory/search",
|
|
body: {
|
|
search_term: context.query,
|
|
limit: resolveMatrixDirectoryLimit(params.limit),
|
|
},
|
|
});
|
|
const results = res.results ?? [];
|
|
return results
|
|
.map((entry) => {
|
|
const userId = entry.user_id?.trim();
|
|
if (!userId) {
|
|
return null;
|
|
}
|
|
return {
|
|
kind: "user",
|
|
id: userId,
|
|
name: entry.display_name?.trim() || undefined,
|
|
handle: entry.display_name ? `@${entry.display_name.trim()}` : undefined,
|
|
raw: entry,
|
|
} satisfies ChannelDirectoryEntry;
|
|
})
|
|
.filter(Boolean) as ChannelDirectoryEntry[];
|
|
}
|
|
|
|
async function resolveMatrixRoomAlias(
|
|
client: MatrixAuthedHttpClient,
|
|
alias: string,
|
|
): Promise<string | null> {
|
|
try {
|
|
const res = await requestMatrixJson<MatrixAliasLookup>(client, {
|
|
method: "GET",
|
|
endpoint: `/_matrix/client/v3/directory/room/${encodeURIComponent(alias)}`,
|
|
});
|
|
return res.room_id?.trim() || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchMatrixRoomName(
|
|
client: MatrixAuthedHttpClient,
|
|
roomId: string,
|
|
): Promise<string | null> {
|
|
try {
|
|
const res = await requestMatrixJson<MatrixRoomNameState>(client, {
|
|
method: "GET",
|
|
endpoint: `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/state/m.room.name`,
|
|
});
|
|
return res.name?.trim() || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function listMatrixDirectoryGroupsLive(
|
|
params: MatrixDirectoryLiveParams,
|
|
): Promise<ChannelDirectoryEntry[]> {
|
|
const query = normalizeQuery(params.query);
|
|
if (!query) {
|
|
return [];
|
|
}
|
|
const directTarget = normalizeMatrixMessagingTarget(query);
|
|
|
|
if (directTarget?.startsWith("!")) {
|
|
return [createGroupDirectoryEntry({ id: directTarget, name: directTarget })];
|
|
}
|
|
|
|
const context = await resolveMatrixDirectoryContext({
|
|
...params,
|
|
query,
|
|
});
|
|
if (!context) {
|
|
return [];
|
|
}
|
|
const { client, queryLower } = context;
|
|
const limit = resolveMatrixDirectoryLimit(params.limit);
|
|
|
|
if (directTarget?.startsWith("#")) {
|
|
const roomId = await resolveMatrixRoomAlias(client, directTarget);
|
|
if (!roomId) {
|
|
return [];
|
|
}
|
|
return [createGroupDirectoryEntry({ id: roomId, name: directTarget, handle: directTarget })];
|
|
}
|
|
|
|
const joined = await requestMatrixJson<MatrixJoinedRoomsResponse>(client, {
|
|
method: "GET",
|
|
endpoint: "/_matrix/client/v3/joined_rooms",
|
|
});
|
|
const rooms = (joined.joined_rooms ?? []).map((roomId) => roomId.trim()).filter(Boolean);
|
|
const results: ChannelDirectoryEntry[] = [];
|
|
|
|
for (const roomId of rooms) {
|
|
const name = await fetchMatrixRoomName(client, roomId);
|
|
if (!name || !name.toLowerCase().includes(queryLower)) {
|
|
continue;
|
|
}
|
|
results.push({
|
|
kind: "group",
|
|
id: roomId,
|
|
name,
|
|
handle: `#${name}`,
|
|
});
|
|
if (results.length >= limit) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|