fix(core): 🐛 确保转发消息前先发送资料卡片
在转发用户消息到话题之前,强制执行资料卡片发送逻辑。这样可以确保用户信息卡片始终处于话题内容的顶部,避免后续回复消息将卡片内容淹没。 主要变更: - 在消息中继逻辑中插入 ensureInfoCardBeforeRelay 调用 - 优化了消息回复 ID 的查询与映射处理逻辑
This commit is contained in:
@@ -732,6 +732,9 @@ async function relayToTopic(msg, u, env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 资料卡片必须先落到话题里,后续用户消息才不会把卡片顶到下面。
|
||||||
|
await ensureInfoCardBeforeRelay(env, u, msg.from, tid, msg.date);
|
||||||
|
|
||||||
let forwardedMsg;
|
let forwardedMsg;
|
||||||
const rawText = msg.text || "";
|
const rawText = msg.text || "";
|
||||||
let topicReplyToMsgId = undefined;
|
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 调用。
|
// * 修改点:注释/移除了面向用户的 “✅ 已送达” 反馈回执 API 调用。
|
||||||
// 此动作去除了多余的底层 fetch() 开销。
|
// 此动作去除了多余的底层 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) {
|
async function sendInfoCardToTopic(env, u, tgUser, tid, date) {
|
||||||
const meta = getUMeta(tgUser, u, date || (Date.now()/1000));
|
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 } : {};
|
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) {
|
async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
||||||
const reply = withReplyTarget(replyToMsgId);
|
const reply = withReplyTarget(replyToMsgId);
|
||||||
const base = { chat_id: uid, ...reply };
|
const base = { chat_id: uid, ...reply };
|
||||||
@@ -943,14 +992,14 @@ async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
|||||||
if (msg.text) {
|
if (msg.text) {
|
||||||
const body = { ...base, text: msg.text };
|
const body = { ...base, text: msg.text };
|
||||||
if (msg.entities) body.entities = msg.entities;
|
if (msg.entities) body.entities = msg.entities;
|
||||||
return api(env.BOT_TOKEN, "sendMessage", body);
|
return apiWithDeliveryFallback(env, "sendMessage", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.photo) {
|
if (msg.photo) {
|
||||||
const body = { ...base, photo: msg.photo[msg.photo.length - 1].file_id };
|
const body = { ...base, photo: msg.photo[msg.photo.length - 1].file_id };
|
||||||
if (msg.caption) body.caption = msg.caption;
|
if (msg.caption) body.caption = msg.caption;
|
||||||
if (msg.caption_entities) body.caption_entities = msg.caption_entities;
|
if (msg.caption_entities) body.caption_entities = msg.caption_entities;
|
||||||
return api(env.BOT_TOKEN, "sendPhoto", body);
|
return apiWithDeliveryFallback(env, "sendPhoto", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaMap = [
|
const mediaMap = [
|
||||||
@@ -968,11 +1017,11 @@ async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
|||||||
const body = { ...base, [param]: msg[field].file_id };
|
const body = { ...base, [param]: msg[field].file_id };
|
||||||
if (msg.caption) body.caption = msg.caption;
|
if (msg.caption) body.caption = msg.caption;
|
||||||
if (msg.caption_entities) body.caption_entities = msg.caption_entities;
|
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) {
|
if (msg.location) {
|
||||||
return api(env.BOT_TOKEN, "sendLocation", {
|
return apiWithDeliveryFallback(env, "sendLocation", {
|
||||||
...base,
|
...base,
|
||||||
latitude: msg.location.latitude,
|
latitude: msg.location.latitude,
|
||||||
longitude: msg.location.longitude
|
longitude: msg.location.longitude
|
||||||
@@ -980,7 +1029,7 @@ async function deliverAdminMessageToUser(msg, uid, replyToMsgId, env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.contact) {
|
if (msg.contact) {
|
||||||
return api(env.BOT_TOKEN, "sendContact", {
|
return apiWithDeliveryFallback(env, "sendContact", {
|
||||||
...base,
|
...base,
|
||||||
phone_number: msg.contact.phone_number,
|
phone_number: msg.contact.phone_number,
|
||||||
first_name: msg.contact.first_name,
|
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,
|
chat_id: uid,
|
||||||
from_chat_id: msg.chat.id,
|
from_chat_id: msg.chat.id,
|
||||||
message_id: msg.message_id,
|
message_id: msg.message_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user