Files
SHiNE-server/shine-UI/js/services/dm-tech-blocks.js
T

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,
};
}