SHA256
145 lines
4.9 KiB
JavaScript
145 lines
4.9 KiB
JavaScript
function defaultParsed(rawText = '') {
|
|
const text = String(rawText || '');
|
|
return {
|
|
rawText: text,
|
|
prefixText: '',
|
|
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 buildDmReplyTechBlock({ baseKey = '' } = {}) {
|
|
const cleanBaseKey = String(baseKey || '').trim();
|
|
if (!cleanBaseKey) return '';
|
|
return `<SHiNE:reply;v=1;id=${cleanBaseKey}>`;
|
|
}
|
|
|
|
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,
|
|
prefixText: text.slice(0, cursor),
|
|
visibleText,
|
|
displayText,
|
|
blocks,
|
|
replyRef,
|
|
callSummary,
|
|
};
|
|
}
|