mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 23:40:45 +00:00
210 lines
5.3 KiB
TypeScript
210 lines
5.3 KiB
TypeScript
import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/matrix";
|
|
import { resolveMatrixAuth } from "./matrix/client.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>>;
|
|
|
|
async function fetchMatrixJson<T>(params: {
|
|
homeserver: string;
|
|
path: string;
|
|
accessToken: string;
|
|
method?: "GET" | "POST";
|
|
body?: unknown;
|
|
}): Promise<T> {
|
|
const res = await fetch(`${params.homeserver}${params.path}`, {
|
|
method: params.method ?? "GET",
|
|
headers: {
|
|
Authorization: `Bearer ${params.accessToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: params.body ? JSON.stringify(params.body) : undefined,
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`Matrix API ${params.path} failed (${res.status}): ${text || "unknown error"}`);
|
|
}
|
|
return (await res.json()) as T;
|
|
}
|
|
|
|
function normalizeQuery(value?: string | null): string {
|
|
return value?.trim().toLowerCase() ?? "";
|
|
}
|
|
|
|
function resolveMatrixDirectoryLimit(limit?: number | null): number {
|
|
return typeof limit === "number" && limit > 0 ? limit : 20;
|
|
}
|
|
|
|
async function resolveMatrixDirectoryContext(
|
|
params: MatrixDirectoryLiveParams,
|
|
): Promise<{ query: string; auth: MatrixResolvedAuth } | null> {
|
|
const query = normalizeQuery(params.query);
|
|
if (!query) {
|
|
return null;
|
|
}
|
|
const auth = await resolveMatrixAuth({ cfg: params.cfg as never, accountId: params.accountId });
|
|
return { query, auth };
|
|
}
|
|
|
|
function createGroupDirectoryEntry(params: {
|
|
id: string;
|
|
name: string;
|
|
handle?: string;
|
|
}): ChannelDirectoryEntry {
|
|
return {
|
|
kind: "group",
|
|
id: params.id,
|
|
name: params.name,
|
|
handle: params.handle,
|
|
} satisfies ChannelDirectoryEntry;
|
|
}
|
|
|
|
export async function listMatrixDirectoryPeersLive(
|
|
params: MatrixDirectoryLiveParams,
|
|
): Promise<ChannelDirectoryEntry[]> {
|
|
const context = await resolveMatrixDirectoryContext(params);
|
|
if (!context) {
|
|
return [];
|
|
}
|
|
const { query, auth } = context;
|
|
const res = await fetchMatrixJson<MatrixUserDirectoryResponse>({
|
|
homeserver: auth.homeserver,
|
|
accessToken: auth.accessToken,
|
|
path: "/_matrix/client/v3/user_directory/search",
|
|
method: "POST",
|
|
body: {
|
|
search_term: 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(
|
|
homeserver: string,
|
|
accessToken: string,
|
|
alias: string,
|
|
): Promise<string | null> {
|
|
try {
|
|
const res = await fetchMatrixJson<MatrixAliasLookup>({
|
|
homeserver,
|
|
accessToken,
|
|
path: `/_matrix/client/v3/directory/room/${encodeURIComponent(alias)}`,
|
|
});
|
|
return res.room_id?.trim() || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchMatrixRoomName(
|
|
homeserver: string,
|
|
accessToken: string,
|
|
roomId: string,
|
|
): Promise<string | null> {
|
|
try {
|
|
const res = await fetchMatrixJson<MatrixRoomNameState>({
|
|
homeserver,
|
|
accessToken,
|
|
path: `/_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 context = await resolveMatrixDirectoryContext(params);
|
|
if (!context) {
|
|
return [];
|
|
}
|
|
const { query, auth } = context;
|
|
const limit = resolveMatrixDirectoryLimit(params.limit);
|
|
|
|
if (query.startsWith("#")) {
|
|
const roomId = await resolveMatrixRoomAlias(auth.homeserver, auth.accessToken, query);
|
|
if (!roomId) {
|
|
return [];
|
|
}
|
|
return [createGroupDirectoryEntry({ id: roomId, name: query, handle: query })];
|
|
}
|
|
|
|
if (query.startsWith("!")) {
|
|
const originalId = params.query?.trim() ?? query;
|
|
return [createGroupDirectoryEntry({ id: originalId, name: originalId })];
|
|
}
|
|
|
|
const joined = await fetchMatrixJson<MatrixJoinedRoomsResponse>({
|
|
homeserver: auth.homeserver,
|
|
accessToken: auth.accessToken,
|
|
path: "/_matrix/client/v3/joined_rooms",
|
|
});
|
|
const rooms = joined.joined_rooms ?? [];
|
|
const results: ChannelDirectoryEntry[] = [];
|
|
|
|
for (const roomId of rooms) {
|
|
const name = await fetchMatrixRoomName(auth.homeserver, auth.accessToken, roomId);
|
|
if (!name) {
|
|
continue;
|
|
}
|
|
if (!name.toLowerCase().includes(query)) {
|
|
continue;
|
|
}
|
|
results.push({
|
|
kind: "group",
|
|
id: roomId,
|
|
name,
|
|
handle: `#${name}`,
|
|
});
|
|
if (results.length >= limit) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|