forked from bai/curriculum-project-hub
fix: resolve Feishu sender names via basic profile API
This commit is contained in:
+29
-10
@@ -96,11 +96,18 @@ export interface CardActionEvent {
|
||||
readonly token?: string;
|
||||
}
|
||||
|
||||
type ContactUserResponse = {
|
||||
interface ContactBasicUser {
|
||||
readonly name?: string | undefined;
|
||||
readonly i18n_name?: {
|
||||
readonly zh_cn?: string | undefined;
|
||||
readonly en_us?: string | undefined;
|
||||
readonly ja_jp?: string | undefined;
|
||||
} | undefined;
|
||||
}
|
||||
|
||||
type ContactUserBasicBatchResponse = {
|
||||
readonly data?: {
|
||||
readonly user?: {
|
||||
readonly name?: string | undefined;
|
||||
} | undefined;
|
||||
readonly users?: readonly ContactBasicUser[] | undefined;
|
||||
} | undefined;
|
||||
} | null;
|
||||
|
||||
@@ -393,13 +400,12 @@ export async function resolveSenderName(
|
||||
|
||||
const fetchName = async (id: string): Promise<string | null> => {
|
||||
try {
|
||||
const res = await rt.client.request({
|
||||
method: "GET",
|
||||
url: `/open-apis/contact/v3/users/${encodeURIComponent(id)}`,
|
||||
const res = await rt.client.contact.v3.user.basicBatch({
|
||||
params: { user_id_type: "open_id" },
|
||||
}) as ContactUserResponse;
|
||||
const name = res?.data?.user?.name;
|
||||
return typeof name === "string" && name !== "" ? name : null;
|
||||
data: { user_ids: [id] },
|
||||
}) as ContactUserBasicBatchResponse;
|
||||
const user = res?.data?.users?.[0];
|
||||
return user === undefined ? null : contactBasicUserName(user);
|
||||
} catch (e) {
|
||||
rt.logger.warn({ openId: id, err: e instanceof Error ? e.message : String(e) }, "resolveSenderName failed");
|
||||
return null;
|
||||
@@ -412,6 +418,19 @@ export async function resolveSenderName(
|
||||
return fetchName(openId);
|
||||
}
|
||||
|
||||
function contactBasicUserName(user: ContactBasicUser): string | null {
|
||||
const candidates = [
|
||||
user.name,
|
||||
user.i18n_name?.zh_cn,
|
||||
user.i18n_name?.en_us,
|
||||
user.i18n_name?.ja_jp,
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate === "string" && candidate.trim() !== "") return candidate.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- File helpers ---
|
||||
|
||||
/** Download a file/image from a Feishu message to a local path. */
|
||||
|
||||
@@ -2,7 +2,8 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { sendFile, sendLongText, sendTextMessage, type FeishuRuntime } from "../../src/feishu/client.js";
|
||||
import { resolveSenderName, sendFile, sendLongText, sendTextMessage, type FeishuRuntime } from "../../src/feishu/client.js";
|
||||
import { SenderNameCache } from "../../src/feishu/senderCache.js";
|
||||
|
||||
describe("Feishu client helpers", () => {
|
||||
it("accepts top-level file_key from the Lark SDK file upload response", async () => {
|
||||
@@ -47,14 +48,41 @@ describe("Feishu client helpers", () => {
|
||||
});
|
||||
expect(sentTexts.map((text) => text.length)).toEqual([8000, 8000, 5]);
|
||||
});
|
||||
|
||||
it("resolves sender names through the basic contact API and caches hits", async () => {
|
||||
const contactBasicBatch = vi.fn(async () => ({ data: { users: [{ name: "Jiarong" }] } }));
|
||||
const request = vi.fn();
|
||||
const rt = mockRuntime({ fileCreate: vi.fn(), messageCreate: vi.fn(), contactBasicBatch, request });
|
||||
const cache = new SenderNameCache();
|
||||
|
||||
await expect(resolveSenderName(rt, "ou_user_1", cache)).resolves.toBe("Jiarong");
|
||||
await expect(resolveSenderName(rt, "ou_user_1", cache)).resolves.toBe("Jiarong");
|
||||
|
||||
expect(contactBasicBatch).toHaveBeenCalledTimes(1);
|
||||
expect(contactBasicBatch).toHaveBeenCalledWith({
|
||||
params: { user_id_type: "open_id" },
|
||||
data: { user_ids: ["ou_user_1"] },
|
||||
});
|
||||
expect(request).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function mockRuntime(options: {
|
||||
readonly fileCreate: (payload: unknown) => Promise<unknown>;
|
||||
readonly messageCreate: (payload: unknown) => Promise<unknown>;
|
||||
readonly contactBasicBatch?: (payload: unknown) => Promise<unknown>;
|
||||
readonly request?: (payload: unknown) => Promise<unknown>;
|
||||
}): FeishuRuntime {
|
||||
return {
|
||||
client: {
|
||||
request: options.request,
|
||||
contact: {
|
||||
v3: {
|
||||
user: {
|
||||
basicBatch: options.contactBasicBatch,
|
||||
},
|
||||
},
|
||||
},
|
||||
im: {
|
||||
v1: {
|
||||
file: { create: options.fileCreate },
|
||||
|
||||
Reference in New Issue
Block a user