SHA256
Добавить SHiNE теги в DM и безопасный рендер превью
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
function defaultParsed(rawText = '') {
|
||||
const text = String(rawText || '');
|
||||
return {
|
||||
rawText: text,
|
||||
visibleText: text,
|
||||
displayText: text,
|
||||
blocks: [],
|
||||
replyRef: null,
|
||||
callSummary: null,
|
||||
};
|
||||
}
|
||||
|
||||
function parseReplyId(value = '') {
|
||||
const parts = String(value || '').split('|');
|
||||
if (parts.length !== 4) return null;
|
||||
const fromLogin = String(parts[0] || '').trim();
|
||||
const toLogin = String(parts[1] || '').trim();
|
||||
const timeMs = Number(parts[2] || 0);
|
||||
const nonce = Number(parts[3] || 0);
|
||||
if (!fromLogin || !toLogin || !Number.isFinite(timeMs) || timeMs <= 0 || !Number.isFinite(nonce) || nonce < 0) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
fromLogin,
|
||||
toLogin,
|
||||
timeMs,
|
||||
nonce,
|
||||
baseKey: `${fromLogin}|${toLogin}|${timeMs}|${nonce}`,
|
||||
};
|
||||
}
|
||||
|
||||
function formatCallDuration(totalSeconds) {
|
||||
const total = Math.max(0, Math.floor(Number(totalSeconds || 0)));
|
||||
const hours = Math.floor(total / 3600);
|
||||
const minutes = Math.floor((total % 3600) / 60);
|
||||
const seconds = total % 60;
|
||||
if (hours > 0) {
|
||||
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
}
|
||||
return `${minutes}:${String(seconds).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function buildCallDisplayText(callSummary) {
|
||||
if (!callSummary) return '';
|
||||
if (callSummary.status === 'completed') {
|
||||
return `Звонок: ${formatCallDuration(callSummary.durationSec)}`;
|
||||
}
|
||||
const reason = String(callSummary.reason || '').trim().toLowerCase();
|
||||
if (reason === 'offline') return 'Звонил, но недозвонился: абонент не в сети';
|
||||
if (reason === 'no_answer') return 'Звонил, но недозвонился: нет ответа';
|
||||
if (reason === 'connect_failed') return 'Звонил, но недозвонился: не удалось установить соединение';
|
||||
if (reason === 'busy') return 'Звонил, но недозвонился: абонент занят';
|
||||
if (reason === 'declined') return 'Звонил, но недозвонился: звонок отклонён';
|
||||
return 'Звонил, но недозвонился';
|
||||
}
|
||||
|
||||
export function sanitizeUserDmTextForSend(rawText = '') {
|
||||
const text = String(rawText || '');
|
||||
return text.startsWith('<SHiNE:') ? `< SHiNE:${text.slice(7)}` : text;
|
||||
}
|
||||
|
||||
export function buildDmCallTechBlock({ status = '', durationSec = 0, reason = '' } = {}) {
|
||||
const cleanStatus = String(status || '').trim().toLowerCase();
|
||||
if (cleanStatus === 'completed') {
|
||||
return `<SHiNE:call;v=1;status=completed;duration=${Math.max(0, Math.floor(Number(durationSec || 0)))}>`;
|
||||
}
|
||||
const cleanReason = String(reason || '').trim().toLowerCase();
|
||||
return `<SHiNE:call;v=1;status=failed;reason=${cleanReason || 'connect_failed'}>`;
|
||||
}
|
||||
|
||||
export function parseDmTechBlocks(rawText = '') {
|
||||
const text = String(rawText || '');
|
||||
if (!text.startsWith('<SHiNE:')) return defaultParsed(text);
|
||||
|
||||
const blocks = [];
|
||||
let cursor = 0;
|
||||
let replyRef = null;
|
||||
let callSummary = null;
|
||||
|
||||
while (text.startsWith('<SHiNE:', cursor)) {
|
||||
const end = text.indexOf('>', cursor);
|
||||
if (end < 0) {
|
||||
if (!blocks.length) return defaultParsed(text);
|
||||
break;
|
||||
}
|
||||
|
||||
const inner = text.slice(cursor + 7, end);
|
||||
const segments = inner.split(';').map((part) => String(part || '').trim()).filter(Boolean);
|
||||
if (!segments.length) {
|
||||
if (!blocks.length) return defaultParsed(text);
|
||||
break;
|
||||
}
|
||||
|
||||
const kind = String(segments[0] || '').trim().toLowerCase();
|
||||
const fields = {};
|
||||
for (let i = 1; i < segments.length; i += 1) {
|
||||
const part = segments[i];
|
||||
const eq = part.indexOf('=');
|
||||
if (eq <= 0) continue;
|
||||
const key = part.slice(0, eq).trim().toLowerCase();
|
||||
const value = part.slice(eq + 1).trim();
|
||||
if (key) fields[key] = value;
|
||||
}
|
||||
|
||||
const block = { kind, version: Number(fields.v || 0), fields };
|
||||
blocks.push(block);
|
||||
|
||||
if (kind === 'reply' && !replyRef) {
|
||||
replyRef = parseReplyId(fields.id || '');
|
||||
} else if (kind === 'call' && !callSummary) {
|
||||
const status = String(fields.status || '').trim().toLowerCase();
|
||||
if (status === 'completed') {
|
||||
const durationSec = Math.max(0, Math.floor(Number(fields.duration || 0)));
|
||||
callSummary = { status: 'completed', durationSec };
|
||||
} else {
|
||||
callSummary = {
|
||||
status: 'failed',
|
||||
reason: String(fields.reason || '').trim().toLowerCase(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
cursor = end + 1;
|
||||
}
|
||||
|
||||
const visibleText = text.slice(cursor);
|
||||
const displayText = callSummary ? buildCallDisplayText(callSummary) : visibleText;
|
||||
return {
|
||||
rawText: text,
|
||||
visibleText,
|
||||
displayText,
|
||||
blocks,
|
||||
replyRef,
|
||||
callSummary,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user