fix(core): 🐛 确保转发消息前先发送资料卡片
在转发用户消息到话题之前,强制执行资料卡片发送逻辑。这样可以确保用户信息卡片始终处于话题内容的顶部,避免后续回复消息将卡片内容淹没。 主要变更: - 在消息中继逻辑中插入 ensureInfoCardBeforeRelay 调用 - 优化了消息回复 ID 的查询与映射处理逻辑
This commit is contained in:
@@ -732,6 +732,9 @@ async function relayToTopic(msg, u, env) {
|
||||
}
|
||||
|
||||
try {
|
||||
// 资料卡片必须先落到话题里,后续用户消息才不会把卡片顶到下面。
|
||||
await ensureInfoCardBeforeRelay(env, u, msg.from, tid, msg.date);
|
||||
|
||||
let forwardedMsg;
|
||||
const rawText = msg.text || "";
|
||||
let topicReplyToMsgId = undefined;
|
||||
@@ -784,37 +787,6 @@ async function relayToTopic(msg, u, env) {
|
||||
}
|
||||
}
|
||||
|
||||
// 推送资料卡片流程补偿机制
|
||||
try {
|
||||
let infoDirty = false;
|
||||
if (!u.user_info.card_msg_id) {
|
||||
try {
|
||||
const cardId = await sendInfoCardToTopic(env, u, msg.from, tid, msg.date);
|
||||
if (cardId) {
|
||||
u.user_info.card_msg_id = cardId;
|
||||
u.user_info.join_date = msg.date || (Date.now()/1000);
|
||||
infoDirty = true;
|
||||
}
|
||||
} catch (innerErr) {
|
||||
if (innerErr.message && (innerErr.message.includes("thread") || innerErr.message.includes("not found"))) {
|
||||
throw innerErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 回收临时占位符提升界面整洁度
|
||||
if (u.user_info.dummy_msg_id) {
|
||||
await api(env.BOT_TOKEN, "deleteMessage", { chat_id: env.ADMIN_GROUP_ID, message_id: u.user_info.dummy_msg_id }).catch(() => {});
|
||||
delete u.user_info.dummy_msg_id; infoDirty = true;
|
||||
}
|
||||
if (infoDirty) await updUser(uid, { user_info: u.user_info }, env);
|
||||
} catch (processErr) {
|
||||
if (processErr.message && (processErr.message.includes("thread") || processErr.message.includes("not found"))) {
|
||||
await updUser(uid, { topic_id: null }, env);
|
||||
u.topic_id = null;
|
||||
return relayToTopic(msg, u, env); // 进行一次自愈递归重试
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// * 修改点:注释/移除了面向用户的 “✅ 已送达” 反馈回执 API 调用。
|
||||
// 此动作去除了多余的底层 fetch() 开销。
|
||||
@@ -841,6 +813,29 @@ async function relayToTopic(msg, u, env) {
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureInfoCardBeforeRelay(env, u, tgUser, tid, date) {
|
||||
let infoDirty = false;
|
||||
if (!u.user_info) u.user_info = {};
|
||||
|
||||
if (!u.user_info.card_msg_id) {
|
||||
const cardId = await sendInfoCardToTopic(env, u, tgUser, tid, date);
|
||||
if (cardId) {
|
||||
u.user_info.card_msg_id = cardId;
|
||||
u.user_info.join_date = date || (Date.now()/1000);
|
||||
infoDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 回收临时占位符提升界面整洁度;先发卡片再删占位,确保新 topic 里卡片始终早于用户消息。
|
||||
if (u.user_info.dummy_msg_id) {
|
||||
await api(env.BOT_TOKEN, "deleteMessage", { chat_id: env.ADMIN_GROUP_ID, message_id: u.user_info.dummy_msg_id }).catch(() => {});
|
||||
delete u.user_info.dummy_msg_id;
|
||||
infoDirty = true;
|
||||
}
|
||||
|
||||
if (infoDirty) await updUser(u.user_id, { user_info: u.user_info }, env);
|
||||
}
|
||||
|
||||
// --- 核心:发送用户信息复合卡片 ---
|
||||
async function sendInfoCardToTopic(env, u, tgUser, tid, date) {
|
||||
const meta = getUMeta(tgUser, u, date || (Date.now()/1000));
|
||||
@@ -936,6 +931,60 @@ const withReplyTarget = (replyToMsgId) => {
|
||||
return Number.isFinite(id) ? { reply_to_message_id: id } : {};
|
||||
};
|
||||
|
||||
const isReplyTargetError = (e) => {
|
||||
const msg = (e?.message || "").toLowerCase();
|
||||
const mentionsReply = msg.includes("reply") || msg.includes("replied");
|
||||
return mentionsReply && (msg.includes("not found") || msg.includes("invalid"));
|
||||
};
|
||||
|
||||
const isEntityPayloadError = (e) => {
|
||||
const msg = (e?.message || "").toLowerCase();
|
||||
return msg.includes("entity") || msg.includes("entities") || msg.includes("parse");
|
||||
};
|
||||
|
||||
const withoutReplyTarget = (body) => {
|
||||
const next = { ...body };
|
||||
delete next.reply_to_message_id;
|
||||
return next;
|
||||
};
|
||||
|
||||
const withoutEntityPayload = (body) => {
|
||||
const next = { ...body };
|
||||
delete next.entities;
|
||||
delete next.caption_entities;
|
||||
return next;
|
||||
};
|
||||
|
||||
async function apiWithDeliveryFallback(env, method, body) {
|
||||
try {
|
||||
return await api(env.BOT_TOKEN, method, body);
|
||||
} catch (firstErr) {
|
||||
const candidates = [];
|
||||
const hasReplyTarget = body.reply_to_message_id !== undefined;
|
||||
const hasEntityPayload = body.entities || body.caption_entities;
|
||||
|
||||
if (hasReplyTarget && isReplyTargetError(firstErr)) {
|
||||
candidates.push({ body: withoutReplyTarget(body), reason: "reply target missing" });
|
||||
if (hasEntityPayload) candidates.push({ body: withoutEntityPayload(withoutReplyTarget(body)), reason: "reply target missing + entity fallback" });
|
||||
}
|
||||
if (hasEntityPayload && isEntityPayloadError(firstErr)) {
|
||||
candidates.push({ body: withoutEntityPayload(body), reason: "entity fallback" });
|
||||
if (hasReplyTarget) candidates.push({ body: withoutReplyTarget(withoutEntityPayload(body)), reason: "entity fallback + reply target removed" });
|
||||
}
|
||||
|
||||
let lastErr = firstErr;
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
console.warn(`Delivery fallback [${method}]: ${candidate.reason}`);
|
||||
return await api(env.BOT_TOKEN, method, candidate.body);
|
||||
} catch (candidateErr) {
|
||||
lastErr = candidateErr;
|
||||
}
|
||||
}
|
||||
throw lastErr;
|
||||
}
|
||||
}
|
||||
|
||||
async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
||||
const reply = withReplyTarget(replyToMsgId);
|
||||
const base = { chat_id: uid, ...reply };
|
||||
@@ -943,14 +992,14 @@ async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
||||
if (msg.text) {
|
||||
const body = { ...base, text: msg.text };
|
||||
if (msg.entities) body.entities = msg.entities;
|
||||
return api(env.BOT_TOKEN, "sendMessage", body);
|
||||
return apiWithDeliveryFallback(env, "sendMessage", body);
|
||||
}
|
||||
|
||||
if (msg.photo) {
|
||||
const body = { ...base, photo: msg.photo[msg.photo.length - 1].file_id };
|
||||
if (msg.caption) body.caption = msg.caption;
|
||||
if (msg.caption_entities) body.caption_entities = msg.caption_entities;
|
||||
return api(env.BOT_TOKEN, "sendPhoto", body);
|
||||
return apiWithDeliveryFallback(env, "sendPhoto", body);
|
||||
}
|
||||
|
||||
const mediaMap = [
|
||||
@@ -968,11 +1017,11 @@ async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
||||
const body = { ...base, [param]: msg[field].file_id };
|
||||
if (msg.caption) body.caption = msg.caption;
|
||||
if (msg.caption_entities) body.caption_entities = msg.caption_entities;
|
||||
return api(env.BOT_TOKEN, method, body);
|
||||
return apiWithDeliveryFallback(env, method, body);
|
||||
}
|
||||
|
||||
if (msg.location) {
|
||||
return api(env.BOT_TOKEN, "sendLocation", {
|
||||
return apiWithDeliveryFallback(env, "sendLocation", {
|
||||
...base,
|
||||
latitude: msg.location.latitude,
|
||||
longitude: msg.location.longitude
|
||||
@@ -980,7 +1029,7 @@ async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
||||
}
|
||||
|
||||
if (msg.contact) {
|
||||
return api(env.BOT_TOKEN, "sendContact", {
|
||||
return apiWithDeliveryFallback(env, "sendContact", {
|
||||
...base,
|
||||
phone_number: msg.contact.phone_number,
|
||||
first_name: msg.contact.first_name,
|
||||
@@ -988,7 +1037,7 @@ async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
||||
});
|
||||
}
|
||||
|
||||
return api(env.BOT_TOKEN, "copyMessage", {
|
||||
return apiWithDeliveryFallback(env, "copyMessage", {
|
||||
chat_id: uid,
|
||||
from_chat_id: msg.chat.id,
|
||||
message_id: msg.message_id,
|
||||
|
||||
Reference in New Issue
Block a user