mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 03:12:42 +00:00
refactor(line): share inbound context builder
This commit is contained in:
@@ -136,6 +136,166 @@ function extractMediaPlaceholder(message: MessageEvent["message"]): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LineRouteInfo = ReturnType<typeof resolveAgentRoute>;
|
||||||
|
type LineSourceInfo = ReturnType<typeof getSourceInfo> & { peerId: string };
|
||||||
|
|
||||||
|
function resolveLineConversationLabel(params: {
|
||||||
|
isGroup: boolean;
|
||||||
|
groupId?: string;
|
||||||
|
roomId?: string;
|
||||||
|
senderLabel: string;
|
||||||
|
}): string {
|
||||||
|
return params.isGroup
|
||||||
|
? params.groupId
|
||||||
|
? `group:${params.groupId}`
|
||||||
|
: params.roomId
|
||||||
|
? `room:${params.roomId}`
|
||||||
|
: "unknown-group"
|
||||||
|
: params.senderLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveLineAddresses(params: {
|
||||||
|
isGroup: boolean;
|
||||||
|
groupId?: string;
|
||||||
|
roomId?: string;
|
||||||
|
userId?: string;
|
||||||
|
peerId: string;
|
||||||
|
}): { fromAddress: string; toAddress: string; originatingTo: string } {
|
||||||
|
const fromAddress = params.isGroup
|
||||||
|
? params.groupId
|
||||||
|
? `line:group:${params.groupId}`
|
||||||
|
: params.roomId
|
||||||
|
? `line:room:${params.roomId}`
|
||||||
|
: `line:${params.peerId}`
|
||||||
|
: `line:${params.userId ?? params.peerId}`;
|
||||||
|
const toAddress = params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`;
|
||||||
|
const originatingTo = params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`;
|
||||||
|
return { fromAddress, toAddress, originatingTo };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finalizeLineInboundContext(params: {
|
||||||
|
cfg: OpenClawConfig;
|
||||||
|
account: ResolvedLineAccount;
|
||||||
|
event: MessageEvent | PostbackEvent;
|
||||||
|
route: LineRouteInfo;
|
||||||
|
source: LineSourceInfo;
|
||||||
|
rawBody: string;
|
||||||
|
timestamp: number;
|
||||||
|
messageSid: string;
|
||||||
|
media: {
|
||||||
|
firstPath: string | undefined;
|
||||||
|
firstContentType?: string;
|
||||||
|
paths?: string[];
|
||||||
|
types?: string[];
|
||||||
|
};
|
||||||
|
locationContext?: ReturnType<typeof toLocationContext>;
|
||||||
|
verboseLog: { kind: "inbound" | "postback"; mediaCount?: number };
|
||||||
|
}) {
|
||||||
|
const { fromAddress, toAddress, originatingTo } = resolveLineAddresses({
|
||||||
|
isGroup: params.source.isGroup,
|
||||||
|
groupId: params.source.groupId,
|
||||||
|
roomId: params.source.roomId,
|
||||||
|
userId: params.source.userId,
|
||||||
|
peerId: params.source.peerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const senderId = params.source.userId ?? "unknown";
|
||||||
|
const senderLabel = params.source.userId ? `user:${params.source.userId}` : "unknown";
|
||||||
|
const conversationLabel = resolveLineConversationLabel({
|
||||||
|
isGroup: params.source.isGroup,
|
||||||
|
groupId: params.source.groupId,
|
||||||
|
roomId: params.source.roomId,
|
||||||
|
senderLabel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const storePath = resolveStorePath(params.cfg.session?.store, {
|
||||||
|
agentId: params.route.agentId,
|
||||||
|
});
|
||||||
|
const envelopeOptions = resolveEnvelopeFormatOptions(params.cfg);
|
||||||
|
const previousTimestamp = readSessionUpdatedAt({
|
||||||
|
storePath,
|
||||||
|
sessionKey: params.route.sessionKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = formatInboundEnvelope({
|
||||||
|
channel: "LINE",
|
||||||
|
from: conversationLabel,
|
||||||
|
timestamp: params.timestamp,
|
||||||
|
body: params.rawBody,
|
||||||
|
chatType: params.source.isGroup ? "group" : "direct",
|
||||||
|
sender: {
|
||||||
|
id: senderId,
|
||||||
|
},
|
||||||
|
previousTimestamp,
|
||||||
|
envelope: envelopeOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ctxPayload = finalizeInboundContext({
|
||||||
|
Body: body,
|
||||||
|
BodyForAgent: params.rawBody,
|
||||||
|
RawBody: params.rawBody,
|
||||||
|
CommandBody: params.rawBody,
|
||||||
|
From: fromAddress,
|
||||||
|
To: toAddress,
|
||||||
|
SessionKey: params.route.sessionKey,
|
||||||
|
AccountId: params.route.accountId,
|
||||||
|
ChatType: params.source.isGroup ? "group" : "direct",
|
||||||
|
ConversationLabel: conversationLabel,
|
||||||
|
GroupSubject: params.source.isGroup
|
||||||
|
? (params.source.groupId ?? params.source.roomId)
|
||||||
|
: undefined,
|
||||||
|
SenderId: senderId,
|
||||||
|
Provider: "line",
|
||||||
|
Surface: "line",
|
||||||
|
MessageSid: params.messageSid,
|
||||||
|
Timestamp: params.timestamp,
|
||||||
|
MediaPath: params.media.firstPath,
|
||||||
|
MediaType: params.media.firstContentType,
|
||||||
|
MediaUrl: params.media.firstPath,
|
||||||
|
MediaPaths: params.media.paths,
|
||||||
|
MediaUrls: params.media.paths,
|
||||||
|
MediaTypes: params.media.types,
|
||||||
|
...params.locationContext,
|
||||||
|
OriginatingChannel: "line" as const,
|
||||||
|
OriginatingTo: originatingTo,
|
||||||
|
});
|
||||||
|
|
||||||
|
void recordSessionMetaFromInbound({
|
||||||
|
storePath,
|
||||||
|
sessionKey: ctxPayload.SessionKey ?? params.route.sessionKey,
|
||||||
|
ctx: ctxPayload,
|
||||||
|
}).catch((err) => {
|
||||||
|
logVerbose(`line: failed updating session meta: ${String(err)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!params.source.isGroup) {
|
||||||
|
await updateLastRoute({
|
||||||
|
storePath,
|
||||||
|
sessionKey: params.route.mainSessionKey,
|
||||||
|
deliveryContext: {
|
||||||
|
channel: "line",
|
||||||
|
to: params.source.userId ?? params.source.peerId,
|
||||||
|
accountId: params.route.accountId,
|
||||||
|
},
|
||||||
|
ctx: ctxPayload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
const preview = body.slice(0, 200).replace(/\n/g, "\\n");
|
||||||
|
const mediaInfo =
|
||||||
|
params.verboseLog.kind === "inbound" && (params.verboseLog.mediaCount ?? 0) > 1
|
||||||
|
? ` mediaCount=${params.verboseLog.mediaCount}`
|
||||||
|
: "";
|
||||||
|
const label = params.verboseLog.kind === "inbound" ? "line inbound" : "line postback";
|
||||||
|
logVerbose(
|
||||||
|
`${label}: from=${ctxPayload.From} len=${body.length}${mediaInfo} preview="${preview}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ctxPayload, replyToken: (params.event as { replyToken: string }).replyToken };
|
||||||
|
}
|
||||||
|
|
||||||
export async function buildLineMessageContext(params: BuildLineMessageContextParams) {
|
export async function buildLineMessageContext(params: BuildLineMessageContextParams) {
|
||||||
const { event, allMedia, cfg, account } = params;
|
const { event, allMedia, cfg, account } = params;
|
||||||
|
|
||||||
@@ -176,43 +336,6 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build sender info
|
|
||||||
const senderId = userId ?? "unknown";
|
|
||||||
const senderLabel = userId ? `user:${userId}` : "unknown";
|
|
||||||
|
|
||||||
// Build conversation label
|
|
||||||
const conversationLabel = isGroup
|
|
||||||
? groupId
|
|
||||||
? `group:${groupId}`
|
|
||||||
: roomId
|
|
||||||
? `room:${roomId}`
|
|
||||||
: "unknown-group"
|
|
||||||
: senderLabel;
|
|
||||||
|
|
||||||
const storePath = resolveStorePath(cfg.session?.store, {
|
|
||||||
agentId: route.agentId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
|
|
||||||
const previousTimestamp = readSessionUpdatedAt({
|
|
||||||
storePath,
|
|
||||||
sessionKey: route.sessionKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
const body = formatInboundEnvelope({
|
|
||||||
channel: "LINE",
|
|
||||||
from: conversationLabel,
|
|
||||||
timestamp,
|
|
||||||
body: rawBody,
|
|
||||||
chatType: isGroup ? "group" : "direct",
|
|
||||||
sender: {
|
|
||||||
id: senderId,
|
|
||||||
},
|
|
||||||
previousTimestamp,
|
|
||||||
envelope: envelopeOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build location context if applicable
|
|
||||||
let locationContext: ReturnType<typeof toLocationContext> | undefined;
|
let locationContext: ReturnType<typeof toLocationContext> | undefined;
|
||||||
if (message.type === "location") {
|
if (message.type === "location") {
|
||||||
const loc = message;
|
const loc = message;
|
||||||
@@ -224,76 +347,28 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromAddress = isGroup
|
const { ctxPayload } = await finalizeLineInboundContext({
|
||||||
? groupId
|
cfg,
|
||||||
? `line:group:${groupId}`
|
account,
|
||||||
: roomId
|
event,
|
||||||
? `line:room:${roomId}`
|
route,
|
||||||
: `line:${peerId}`
|
source: { userId, groupId, roomId, isGroup, peerId },
|
||||||
: `line:${userId ?? peerId}`;
|
rawBody,
|
||||||
const toAddress = isGroup ? fromAddress : `line:${userId ?? peerId}`;
|
timestamp,
|
||||||
const originatingTo = isGroup ? fromAddress : `line:${userId ?? peerId}`;
|
messageSid: messageId,
|
||||||
|
media: {
|
||||||
const ctxPayload = finalizeInboundContext({
|
firstPath: allMedia[0]?.path,
|
||||||
Body: body,
|
firstContentType: allMedia[0]?.contentType,
|
||||||
BodyForAgent: rawBody,
|
paths: allMedia.length > 0 ? allMedia.map((m) => m.path) : undefined,
|
||||||
RawBody: rawBody,
|
types:
|
||||||
CommandBody: rawBody,
|
allMedia.length > 0
|
||||||
From: fromAddress,
|
? (allMedia.map((m) => m.contentType).filter(Boolean) as string[])
|
||||||
To: toAddress,
|
: undefined,
|
||||||
SessionKey: route.sessionKey,
|
},
|
||||||
AccountId: route.accountId,
|
locationContext,
|
||||||
ChatType: isGroup ? "group" : "direct",
|
verboseLog: { kind: "inbound", mediaCount: allMedia.length },
|
||||||
ConversationLabel: conversationLabel,
|
|
||||||
GroupSubject: isGroup ? (groupId ?? roomId) : undefined,
|
|
||||||
SenderId: senderId,
|
|
||||||
Provider: "line",
|
|
||||||
Surface: "line",
|
|
||||||
MessageSid: messageId,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
MediaPath: allMedia[0]?.path,
|
|
||||||
MediaType: allMedia[0]?.contentType,
|
|
||||||
MediaUrl: allMedia[0]?.path,
|
|
||||||
MediaPaths: allMedia.length > 0 ? allMedia.map((m) => m.path) : undefined,
|
|
||||||
MediaUrls: allMedia.length > 0 ? allMedia.map((m) => m.path) : undefined,
|
|
||||||
MediaTypes:
|
|
||||||
allMedia.length > 0
|
|
||||||
? (allMedia.map((m) => m.contentType).filter(Boolean) as string[])
|
|
||||||
: undefined,
|
|
||||||
...locationContext,
|
|
||||||
OriginatingChannel: "line" as const,
|
|
||||||
OriginatingTo: originatingTo,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
void recordSessionMetaFromInbound({
|
|
||||||
storePath,
|
|
||||||
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
||||||
ctx: ctxPayload,
|
|
||||||
}).catch((err) => {
|
|
||||||
logVerbose(`line: failed updating session meta: ${String(err)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isGroup) {
|
|
||||||
await updateLastRoute({
|
|
||||||
storePath,
|
|
||||||
sessionKey: route.mainSessionKey,
|
|
||||||
deliveryContext: {
|
|
||||||
channel: "line",
|
|
||||||
to: userId ?? peerId,
|
|
||||||
accountId: route.accountId,
|
|
||||||
},
|
|
||||||
ctx: ctxPayload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldLogVerbose()) {
|
|
||||||
const preview = body.slice(0, 200).replace(/\n/g, "\\n");
|
|
||||||
const mediaInfo = allMedia.length > 1 ? ` mediaCount=${allMedia.length}` : "";
|
|
||||||
logVerbose(
|
|
||||||
`line inbound: from=${ctxPayload.From} len=${body.length}${mediaInfo} preview="${preview}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ctxPayload,
|
ctxPayload,
|
||||||
event,
|
event,
|
||||||
@@ -347,103 +422,25 @@ export async function buildLinePostbackContext(params: {
|
|||||||
rawBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
|
rawBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderId = userId ?? "unknown";
|
const messageSid = event.replyToken ? `postback:${event.replyToken}` : `postback:${timestamp}`;
|
||||||
const senderLabel = userId ? `user:${userId}` : "unknown";
|
const { ctxPayload } = await finalizeLineInboundContext({
|
||||||
|
cfg,
|
||||||
const conversationLabel = isGroup
|
account,
|
||||||
? groupId
|
event,
|
||||||
? `group:${groupId}`
|
route,
|
||||||
: roomId
|
source: { userId, groupId, roomId, isGroup, peerId },
|
||||||
? `room:${roomId}`
|
rawBody,
|
||||||
: "unknown-group"
|
|
||||||
: senderLabel;
|
|
||||||
|
|
||||||
const storePath = resolveStorePath(cfg.session?.store, {
|
|
||||||
agentId: route.agentId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
|
|
||||||
const previousTimestamp = readSessionUpdatedAt({
|
|
||||||
storePath,
|
|
||||||
sessionKey: route.sessionKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
const body = formatInboundEnvelope({
|
|
||||||
channel: "LINE",
|
|
||||||
from: conversationLabel,
|
|
||||||
timestamp,
|
timestamp,
|
||||||
body: rawBody,
|
messageSid,
|
||||||
chatType: isGroup ? "group" : "direct",
|
media: {
|
||||||
sender: {
|
firstPath: "",
|
||||||
id: senderId,
|
firstContentType: undefined,
|
||||||
|
paths: undefined,
|
||||||
|
types: undefined,
|
||||||
},
|
},
|
||||||
previousTimestamp,
|
verboseLog: { kind: "postback" },
|
||||||
envelope: envelopeOptions,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const fromAddress = isGroup
|
|
||||||
? groupId
|
|
||||||
? `line:group:${groupId}`
|
|
||||||
: roomId
|
|
||||||
? `line:room:${roomId}`
|
|
||||||
: `line:${peerId}`
|
|
||||||
: `line:${userId ?? peerId}`;
|
|
||||||
const toAddress = isGroup ? fromAddress : `line:${userId ?? peerId}`;
|
|
||||||
const originatingTo = isGroup ? fromAddress : `line:${userId ?? peerId}`;
|
|
||||||
|
|
||||||
const ctxPayload = finalizeInboundContext({
|
|
||||||
Body: body,
|
|
||||||
BodyForAgent: rawBody,
|
|
||||||
RawBody: rawBody,
|
|
||||||
CommandBody: rawBody,
|
|
||||||
From: fromAddress,
|
|
||||||
To: toAddress,
|
|
||||||
SessionKey: route.sessionKey,
|
|
||||||
AccountId: route.accountId,
|
|
||||||
ChatType: isGroup ? "group" : "direct",
|
|
||||||
ConversationLabel: conversationLabel,
|
|
||||||
GroupSubject: isGroup ? (groupId ?? roomId) : undefined,
|
|
||||||
SenderId: senderId,
|
|
||||||
Provider: "line",
|
|
||||||
Surface: "line",
|
|
||||||
MessageSid: event.replyToken ? `postback:${event.replyToken}` : `postback:${timestamp}`,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
MediaPath: "",
|
|
||||||
MediaType: undefined,
|
|
||||||
MediaUrl: "",
|
|
||||||
MediaPaths: undefined,
|
|
||||||
MediaUrls: undefined,
|
|
||||||
MediaTypes: undefined,
|
|
||||||
OriginatingChannel: "line" as const,
|
|
||||||
OriginatingTo: originatingTo,
|
|
||||||
});
|
|
||||||
|
|
||||||
void recordSessionMetaFromInbound({
|
|
||||||
storePath,
|
|
||||||
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
||||||
ctx: ctxPayload,
|
|
||||||
}).catch((err) => {
|
|
||||||
logVerbose(`line: failed updating session meta: ${String(err)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isGroup) {
|
|
||||||
await updateLastRoute({
|
|
||||||
storePath,
|
|
||||||
sessionKey: route.mainSessionKey,
|
|
||||||
deliveryContext: {
|
|
||||||
channel: "line",
|
|
||||||
to: userId ?? peerId,
|
|
||||||
accountId: route.accountId,
|
|
||||||
},
|
|
||||||
ctx: ctxPayload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldLogVerbose()) {
|
|
||||||
const preview = body.slice(0, 200).replace(/\n/g, "\\n");
|
|
||||||
logVerbose(`line postback: from=${ctxPayload.From} len=${body.length} preview="${preview}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ctxPayload,
|
ctxPayload,
|
||||||
event,
|
event,
|
||||||
|
|||||||
Reference in New Issue
Block a user