diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelMetaTextParser.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelMetaTextParser.java index df48579e..57bd4ebb 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelMetaTextParser.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/channels/ChannelMetaTextParser.java @@ -6,6 +6,8 @@ import java.util.Map; public final class ChannelMetaTextParser { public static final int MAX_TITLE_CHARS = 50; public static final int MAX_DESCRIPTION_CHARS = 250; + private static final String LEGACY_PREFIX = "', offset); if (end < 0) throw new IllegalArgumentException("bad_channel_meta_tag"); - String body = text.substring(offset + " fields = parseFields(body.substring("avatar;".length())); + String rawFields = body.startsWith("ava;") + ? body.substring("ava;".length()) + : body.substring("avatar;".length()); + Map fields = parseFields(rawFields); if (!"1".equals(fields.get("v"))) throw new IllegalArgumentException("bad_channel_meta_avatar_version"); String ar = String.valueOf(fields.getOrDefault("ar", "")).trim(); String sha256 = String.valueOf(fields.getOrDefault("sha256", "")).trim().toLowerCase(); - String sizeRaw = String.valueOf(fields.getOrDefault("size", "")).trim(); + String sizeRaw = String.valueOf(fields.containsKey("sz") ? fields.get("sz") : fields.getOrDefault("size", "")).trim(); if (!ar.matches("^[A-Za-z0-9_-]{43}$")) throw new IllegalArgumentException("bad_channel_meta_avatar_ar"); if (!sha256.matches("^[0-9a-f]{64}$")) throw new IllegalArgumentException("bad_channel_meta_avatar_sha256"); long size; @@ -78,6 +84,16 @@ public final class ChannelMetaTextParser { return new Avatar(ar, sha256, size); } + private static boolean startsWithTagPrefix(String text, int offset) { + return text.startsWith(LEGACY_PREFIX, offset) || text.startsWith(SHORT_PREFIX, offset); + } + + private static int tagPrefixLength(String text, int offset) { + if (text.startsWith(LEGACY_PREFIX, offset)) return LEGACY_PREFIX.length(); + if (text.startsWith(SHORT_PREFIX, offset)) return SHORT_PREFIX.length(); + return 0; + } + private static Map parseFields(String raw) { Map out = new HashMap<>(); for (String part : String.valueOf(raw == null ? "" : raw).split(";")) { diff --git a/VERSION.properties b/VERSION.properties index 7042c4e0..0b4bf2ae 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.5.36 -server.version=1.4.11 +client.version=1.5.37 +server.version=1.4.12 diff --git a/docs/API/04_Add_Block_to_Blockchain_API.md b/docs/API/04_Add_Block_to_Blockchain_API.md index 88c6857e..576662cf 100644 --- a/docs/API/04_Add_Block_to_Blockchain_API.md +++ b/docs/API/04_Add_Block_to_Blockchain_API.md @@ -160,20 +160,20 @@ ### Вложения в сообщениях -Для `TEXT_POST`, `TEXT_REPLY`, `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` вложения записываются в начало текста сообщения одним или несколькими тегами `SHiNE:attach v=1` или `v=2`. +Для `TEXT_POST`, `TEXT_REPLY`, `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` вложения записываются в начало текста сообщения одним или несколькими тегами `S:att v=1`. Пример текстового содержимого body: ```text - - + + Текст сообщения ``` Пример вложения с отдельным preview-файлом: ```text - + ``` Сервер хранит это как обычный `TEXT`-блок. Отображение карусели, картинок, видео и карточек файлов делает клиент. Полная спецификация тега находится в `docs/Blockchain/15_TEXT_Attachments.md`. @@ -183,8 +183,8 @@ Для публичного канала начальный профиль пишется одним блоком `TECH_CREATE_CHANNEL`. Поле `channelDescription` содержит meta-текст: ```text - - + + Описание канала ``` @@ -197,12 +197,12 @@ Текстовое содержимое body использует тот же формат полного снимка профиля: ```text - - + + Новое описание канала ``` -Каждый `TEXT_CHANNEL_META` является полным состоянием профиля на момент записи. Если аватара нет, тег `SHiNE:avatar` не пишется. Если описания нет, после meta-тегов не добавляется хвостовой текст. Полная спецификация находится в `docs/Blockchain/16_TEXT_Channel_Meta.md`. +Каждый `TEXT_CHANNEL_META` является полным состоянием профиля на момент записи. Если аватара нет, тег `S:ava` не пишется. Если описания нет, после meta-тегов не добавляется хвостовой текст. Полная спецификация находится в `docs/Blockchain/16_TEXT_Channel_Meta.md`. ## 7. Хватает ли функций сейчас diff --git a/docs/Blockchain/01_Channel_Types_and_CreateChannel.md b/docs/Blockchain/01_Channel_Types_and_CreateChannel.md index 742a7948..e724430f 100644 --- a/docs/Blockchain/01_Channel_Types_and_CreateChannel.md +++ b/docs/Blockchain/01_Channel_Types_and_CreateChannel.md @@ -16,8 +16,8 @@ Payload включает: Для публичных каналов (`channelType=1`) поле является начальным снимком профиля канала и использует тот же текстовый meta-формат, что `TEXT_CHANNEL_META`: ```text - - + + Описание канала. ``` diff --git a/docs/Blockchain/11_TEXT_Blocks.md b/docs/Blockchain/11_TEXT_Blocks.md index c629eb85..226934be 100644 --- a/docs/Blockchain/11_TEXT_Blocks.md +++ b/docs/Blockchain/11_TEXT_Blocks.md @@ -34,7 +34,7 @@ TEXT-тип хранит сообщения, материалы и редакт 7. `subType=90` — `TEXT_CHANNEL_META` - скрытый технический снимок профиля канала; - - содержит line-поля + текст с тегами `SHiNE:title`/`SHiNE:avatar` и описанием; + - содержит line-поля + текст с тегами `S:title`/`S:ava` и описанием; - не отображается как обычное сообщение ленты; - применяется сервером к текущему состоянию канала. @@ -73,7 +73,7 @@ TEXT-тип хранит сообщения, материалы и редакт - Такой edit трактуется как логическое удаление содержимого сообщения. - Для удаления используется именно edit-блок; отдельного `DELETE`-подтипа нет. -## Вложения в текстовых сообщениях (`SHiNE:attach v=1/v=2`) +## Вложения в текстовых сообщениях (`S:att v=1`) Для `TEXT_POST`, `TEXT_REPLY`, `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` клиент может хранить вложения как технические строки в начале обычного `text`. @@ -84,31 +84,31 @@ TEXT-тип хранит сообщения, материалы и редакт Один блок вложения: ```text - + ``` Для файлов с отдельным превью клиент может использовать расширенный вариант: ```text - + ``` Несколько вложений идут подряд в самом начале текста, по одному блоку на строку. После последнего блока идёт обычный пользовательский текст. Обязательные поля: -- `v=1` или `v=2` -- `name` - имя файла, закодированное через `encodeURIComponent` -- `size` - размер файла в байтах +- `v=1` +- `nm` - имя файла, закодированное через `encodeURIComponent` +- `sz` - размер файла в байтах - `sha256` - SHA-256 исходного файла в hex - `ar` - короткий Arweave Transaction ID, без полного URL -- для `v=2` дополнительно могут присутствовать `previewAr` и `previewSha256` +- при наличии превью дополнительно могут присутствовать `preAr` и `preSha256` Правила клиента: -- если сообщение начинается с одного или нескольких валидных `` блоков, новый клиент скрывает эти блоки и показывает карточки вложений; +- если сообщение начинается с одного или нескольких валидных attach-блоков (``, `` или ``), новый клиент скрывает эти блоки и показывает карточки вложений; - если блок битый, клиент может игнорировать только этот блок и продолжить разбор остальных; - старые клиенты без поддержки вложений могут показывать технические строки как обычный текст; - пустой пользовательский текст допустим, если перед ним есть хотя бы один валидный attach-блок. -Файлы хранятся вне блокчейна, в Arweave. В блокчейне остаются только `txId`, имя, размер, SHA-256 и, при наличии отдельного preview-файла, `previewAr/previewSha256`. +Файлы хранятся вне блокчейна, в Arweave. В блокчейне остаются только `txId`, имя, размер, SHA-256 и, при наличии отдельного preview-файла, `preAr/preSha256`. diff --git a/docs/Blockchain/15_TEXT_Attachments.md b/docs/Blockchain/15_TEXT_Attachments.md index 9500a5c3..1896401e 100644 --- a/docs/Blockchain/15_TEXT_Attachments.md +++ b/docs/Blockchain/15_TEXT_Attachments.md @@ -1,4 +1,4 @@ -# Вложения в TEXT-сообщениях (`SHiNE:attach v=1/v=2`) +# Вложения в TEXT-сообщениях (`S:att v=1`) Документ фиксирует текущий формат вложений в текстовых блоках SHiNE. @@ -15,23 +15,23 @@ ## Общий вид -Один attach-блок: +Канонический attach-блок: ```text - + ``` Блок с превью: ```text - + ``` Несколько вложений идут подряд в самом начале текста: ```text - - + + Текст сообщения ``` @@ -41,18 +41,18 @@ ## Поля -Обязательные поля: +Обязательные поля канонического формата: - `v=1` - версия формата attach-блока; -- `name` - имя файла, закодированное через `encodeURIComponent`; -- `size` - размер файла в байтах; +- `nm` - имя файла, закодированное через `encodeURIComponent`; +- `sz` - размер файла в байтах; - `sha256` - SHA-256 исходного файла в hex, 64 символа; - `ar` - короткий Arweave Transaction ID, 43 символа, без gateway URL. -Дополнительные поля для `v=2`: +Дополнительные поля для превью: -- `previewAr` - короткий Arweave Transaction ID файла-превью; -- `previewSha256` - SHA-256 файла-превью в hex. +- `preAr` - короткий Arweave Transaction ID файла-превью; +- `preSha256` - SHA-256 файла-превью в hex. ## Хранение файла @@ -63,7 +63,7 @@ - SHA-256; - Arweave `txId`. -Опционально для видео или больших изображений может храниться отдельный второй файл-превью. В таком случае основной attach-блок остаётся одним, но дополнительно указывает `previewAr/previewSha256`. +Опционально для видео или больших изображений может храниться отдельный второй файл-превью. В таком случае основной attach-блок остаётся одним, но дополнительно указывает `preAr/preSha256`. ## Создание вложения в UI @@ -78,7 +78,7 @@ UI поддерживает три сценария: - основной видеофайл; - отдельное изображение-превью. -После успешной загрузки в журнале хранится один элемент основного файла, но с полями `previewAr/previewSha256`. В UI такой элемент помечается как файл `С превью`. +После успешной загрузки в журнале хранится один элемент основного файла, но с полями `preAr/preSha256`. В UI такой элемент помечается как файл `С превью`. При ручном добавлении существующего `txId` для видео UI также позволяет вручную указать `txId` файла-превью. @@ -99,7 +99,7 @@ UI поддерживает три сценария: - изображение показывает как ограниченное по размеру превью; - видео показывает как превью с кнопкой воспроизведения и открывает большой HTML5-плеер по нажатию; - обычный файл показывает как карточку с именем, расширением, размером и скачиванием; -6. если у видео есть `previewAr/previewSha256`, использует отдельный preview-файл как `poster` и как большую превью-плитку в журнале загрузок; +6. если у видео есть `preAr/preSha256`, использует отдельный preview-файл как `poster` и как большую превью-плитку в журнале загрузок; 7. при перелистывании останавливает воспроизводящееся видео; 8. если attach-блок битый, игнорирует только этот блок и продолжает отображать сообщение. @@ -118,7 +118,9 @@ UI поддерживает три сценария: - `ar` допускает только короткий Arweave `txId`, полный URL не используется; - MIME type не записывается, поэтому UI определяет image/video/file по расширению имени файла; -- старые блоки `v=1` без превью остаются валидными и читаются без изменений; -- `previewAr/previewSha256` используются только если присутствуют оба поля и оба валидны; +- старые блоки ``, промежуточные `` и новые `` читаются одинаково; +- старые поля `name/size/previewAr/previewSha256` и новые `nm/sz/preAr/preSha256` читаются одинаково; +- старые блоки `v=2` продолжают читаться как legacy-форма вложения с превью; +- `preAr/preSha256` используются только если присутствуют оба поля и оба валидны; - MIME type, width, height, duration и thumbnail не записываются в блокчейн отдельными полями; - проверка существующего `txId` скачивает файл локально через gateway, поэтому UI ограничивает максимальный размер такой проверки. diff --git a/docs/Blockchain/16_TEXT_Channel_Meta.md b/docs/Blockchain/16_TEXT_Channel_Meta.md index 211b1832..6ff73154 100644 --- a/docs/Blockchain/16_TEXT_Channel_Meta.md +++ b/docs/Blockchain/16_TEXT_Channel_Meta.md @@ -18,15 +18,15 @@ В начале текста могут идти технические теги, после них обычный текст описания: ```text - - + + Описание канала. ``` Поддерживаемые теги: -- `` — человекочитаемое имя канала. -- `` — аватар канала в Arweave. +- `` — человекочитаемое имя канала. +- `` — аватар канала в Arweave. ## Правила @@ -44,7 +44,7 @@ - Длина описания — максимум 250 Unicode code points. - `avatar.ar` — Arweave transaction id из 43 символов. - `avatar.sha256` — 64 hex-символа. -- `avatar.size` — положительный размер файла в байтах. +- `avatar.sz` — положительный размер файла в байтах. Если meta-блок невалиден, сервер не применяет его целиком. @@ -59,3 +59,9 @@ ## Замена старых команд Команда `/.desc` больше не используется и не применяется сервером. Описание канала меняется только через `TEXT_CHANNEL_META`. + +## Совместимость + +- Новый UI пишет сокращённые теги `` и ``. +- Серверное чтение поддерживает и старые теги `` / ``. +- Для размера аватара чтение поддерживает и старое поле `size`, и новое поле `sz`. diff --git a/docs/Blockchain/CHANGELOG.md b/docs/Blockchain/CHANGELOG.md index e88c277e..33b7595c 100644 --- a/docs/Blockchain/CHANGELOG.md +++ b/docs/Blockchain/CHANGELOG.md @@ -1,5 +1,17 @@ # История изменений документации блокчейна +## 2026-08-12 13:00:00 +0400 +- Базовый коммит-ориентир: `working tree`. +- Канонический текстовый формат служебных тегов сокращён: + - `TEXT_CHANNEL_META` теперь пишет `` и ``; + - вложения теперь пишутся как ``; + - превью во вложениях задаётся полями `preAr/preSha256` без отдельной новой версии `v=2`; + - DM-вставки теперь пишутся с префиксом `` продолжают поддерживаться; + - старые поля `name/size/previewAr/previewSha256` продолжают поддерживаться; + - legacy-вложения `v=2` продолжают читаться как вложения с превью. + ## 2026-08-09 23:30:06 +0400 - Базовый коммит-ориентир: `ee185cf`. - Нумерация `STATUS_ACTION` уточнена под дневник действий: diff --git a/docs/Blockchain/README.md b/docs/Blockchain/README.md index 822b3430..17958027 100644 --- a/docs/Blockchain/README.md +++ b/docs/Blockchain/README.md @@ -20,7 +20,7 @@ 8. [15_STATUS_ACTION_Blocks.md](./15_STATUS_ACTION_Blocks.md) Статусные действия пользователя (`msg_type=5`). 9. [16_TEXT_Attachments.md](./16_TEXT_Attachments.md) - Вложения в TEXT-сообщениях через `SHiNE:attach v=1/v=2`, включая опциональные `previewAr/previewSha256` для видео и крупных изображений. + Вложения в TEXT-сообщениях через `S:att v=1`, включая опциональные `preAr/preSha256` для видео и крупных изображений. 10. [16_TEXT_Channel_Meta.md](./16_TEXT_Channel_Meta.md) Скрытый `TEXT_CHANNEL_META` для профиля канала. 11. [01_Channel_Types_and_CreateChannel.md](./01_Channel_Types_and_CreateChannel.md) diff --git a/docs/Personal_Messages/README.md b/docs/Personal_Messages/README.md index 2ec4490a..0f24f649 100644 --- a/docs/Personal_Messages/README.md +++ b/docs/Personal_Messages/README.md @@ -6,7 +6,7 @@ - `docs/Personal_Messages/Протокол_DM_v1.md` — логика протокола, роли API, серверное поведение, routing по `access_servers` - `docs/Personal_Messages/Формат_DM_v1.md` — точный бинарный формат контейнера `SHiNE_DM` -- `docs/Personal_Messages/Технические_вставки_DM_v1.md` — формат специальных `` вставок внутри plaintext DM после расшифровки +- `docs/Personal_Messages/Технические_вставки_DM_v1.md` — формат специальных `` вставок внутри plaintext DM после расшифровки Исторический устаревший документ сохранён отдельно: diff --git a/docs/Personal_Messages/Протокол_DM_v1.md b/docs/Personal_Messages/Протокол_DM_v1.md index 3798f1bb..50fa4ad8 100644 --- a/docs/Personal_Messages/Протокол_DM_v1.md +++ b/docs/Personal_Messages/Протокол_DM_v1.md @@ -96,7 +96,7 @@ - если формат понятен, но расшифровка не удалась, показывает `Не удалось расшифровать сообщение`; - если `body` повреждён или структурно битый, показывает `Сообщение повреждено`. -После успешной расшифровки plaintext может дополнительно содержать специальные клиентские вставки `` в начале текста. +После успешной расшифровки plaintext может дополнительно содержать специальные клиентские вставки `` в начале текста. Legacy-вставки `` также продолжают поддерживаться при чтении. Эти вставки относятся уже к уровню UI/plaintext, а не к уровню серверного DM-envelope. ### 2.4. Источник истины по пользователю diff --git a/docs/Personal_Messages/Технические_вставки_DM_v1.md b/docs/Personal_Messages/Технические_вставки_DM_v1.md index ded7a5fd..436ca0b1 100644 --- a/docs/Personal_Messages/Технические_вставки_DM_v1.md +++ b/docs/Personal_Messages/Технические_вставки_DM_v1.md @@ -18,7 +18,7 @@ Если в начале plaintext стоит один или несколько специальных блоков формата: ```text - + ``` то клиент трактует их как технические вставки. @@ -26,14 +26,14 @@ Техническими считаются только блоки, которые: - стоят строго в начале plaintext; -- начинаются с точного префикса ``. -Если текст не начинается с `` в начале plaintext: +Все корректно распознанные блоки `` или `` в начале plaintext: - не показываются пользователю как сырой текст; - используются клиентом для UI-логики; @@ -45,7 +45,7 @@ Перед отправкой обычного текстового сообщения клиент обязан проверить: -- если пользовательский текст начинается с ` + ``` Правила: @@ -69,14 +69,15 @@ - параметры отделяются `;`; - ключ и значение отделяются `=`; - значения не экранируются в v1; -- формат чувствителен к точному префиксу `Текст ответа +Текст ответа ``` Где поле `id` — это логический идентификатор сообщения: @@ -91,14 +92,14 @@ fromLogin|toLogin|timeMs|nonce - после него может идти обычный текст ответа; - официальный UI формирует такой блок при отправке ответа через пункт `Ответить` в меню сообщения; - если клиент не находит сообщение, на которое ссылается `reply`, он просто не показывает reply-preview; -- в таком случае само сообщение отображается как обычный текст без блока ``. +- в таком случае само сообщение отображается как обычный текст без блока ``. ## 6. Тип `call` ### Успешный звонок ```text - + ``` Где: @@ -108,7 +109,7 @@ fromLogin|toLogin|timeMs|nonce ### Неуспешный звонок ```text - + ``` Допустимые причины в v1: @@ -130,7 +131,7 @@ fromLogin|toLogin|timeMs|nonce Официальный UI SHiNE в v1: -- скрывает все корректные `` блоки в начале plaintext; +- скрывает все корректные `` и legacy `` блоки в начале plaintext; - для `call` строит специальный человекочитаемый текст: - `Звонок: M:SS` - `Звонок: H:MM:SS` diff --git a/shine-UI/js/services/attachment-format.js b/shine-UI/js/services/attachment-format.js index 67291f92..8eda099d 100644 --- a/shine-UI/js/services/attachment-format.js +++ b/shine-UI/js/services/attachment-format.js @@ -1,7 +1,6 @@ import { buildArweaveDataUrl, validateArweaveTxId, validateSha256Hex } from './arweave-file-service.js'; -const ATTACH_PREFIX = ']*)>\n?/u; +const ATTACH_BLOCK_RE = /^<(?:SHiNE|S):(attach|att);([^>]*)>\n?/u; export const MAX_MESSAGE_ATTACHMENTS = 10; const RECENT_UNAVAILABLE_MS = 20 * 60 * 1000; const IMAGE_EXTENSIONS = new Set(['apng', 'avif', 'bmp', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp']); @@ -33,8 +32,8 @@ function normalizeName(name) { } function normalizePreview(input = {}) { - const previewTxId = String(input.previewAr || input.ar || input.txId || '').trim(); - const previewSha256Hex = String(input.previewSha256 || input.sha256 || input.sha256Hex || '').trim().toLowerCase(); + const previewTxId = String(input.preAr || input.previewAr || input.ar || input.txId || '').trim(); + const previewSha256Hex = String(input.preSha256 || input.previewSha256 || input.sha256 || input.sha256Hex || '').trim().toLowerCase(); if (!previewTxId || !previewSha256Hex) return null; if (!validateArweaveTxId(previewTxId)) return null; if (!validateSha256Hex(previewSha256Hex)) return null; @@ -47,9 +46,11 @@ function normalizePreview(input = {}) { export function normalizeAttachment(input = {}) { const txId = String(input.ar || input.txId || '').trim(); const sha256Hex = String(input.sha256 || input.sha256Hex || '').trim().toLowerCase(); - const size = Number(input.size || input.sizeBytes || 0); - const name = normalizeName(input.name || input.fileName || 'file'); + const size = Number(input.sz || input.size || input.sizeBytes || 0); + const name = normalizeName(input.nm || input.name || input.fileName || 'file'); const preview = normalizePreview(input.preview || { + preAr: input.preAr, + preSha256: input.preSha256, previewAr: input.previewAr, previewSha256: input.previewSha256, }); @@ -75,9 +76,9 @@ export function buildAttachmentBlock(attachment) { const item = normalizeAttachment(attachment); const encodedName = encodeURIComponent(item.name); const previewFields = item.preview - ? `;previewAr=${item.preview.ar};previewSha256=${item.preview.sha256}` + ? `;preAr=${item.preview.ar};preSha256=${item.preview.sha256}` : ''; - return ``; + return ``; } export function composeMessageWithAttachments(text, attachments = []) { @@ -105,16 +106,22 @@ export function parseMessageAttachments(rawText) { let rest = String(rawText || ''); const attachments = []; - while (rest.startsWith(ATTACH_PREFIX)) { + while (true) { const match = rest.match(ATTACH_BLOCK_RE); if (!match) break; - const fields = parseFields(match[1]); + const fields = parseFields(match[2]); try { + const version = Number(fields.v || 0); + if (version !== 1 && version !== 2) { + throw new Error('unsupported attachment version'); + } attachments.push(normalizeAttachment({ - name: decodeURIComponent(String(fields.name || 'file')), - size: fields.size, + nm: decodeURIComponent(String(fields.nm || fields.name || 'file')), + sz: fields.sz || fields.size, sha256: fields.sha256, ar: fields.ar, + preAr: fields.preAr, + preSha256: fields.preSha256, previewAr: fields.previewAr, previewSha256: fields.previewSha256, })); diff --git a/shine-UI/js/services/auth-service.js b/shine-UI/js/services/auth-service.js index b53c860c..3db9363d 100644 --- a/shine-UI/js/services/auth-service.js +++ b/shine-UI/js/services/auth-service.js @@ -795,7 +795,7 @@ function composeChannelMetaText({ title = '', description = '', avatar = null } const cleanTitle = validateChannelMetaTitle(title); const cleanDescription = normalizeChannelMetaDescription(description); const rows = []; - if (cleanTitle) rows.push(``); + if (cleanTitle) rows.push(``); if (avatar?.ar) { const size = Number(avatar.size || 0); const sha256 = String(avatar.sha256 || '').trim().toLowerCase(); @@ -803,7 +803,7 @@ function composeChannelMetaText({ title = '', description = '', avatar = null } if (!Number.isInteger(size) || size <= 0) throw new Error('Некорректный размер аватара.'); if (!/^[0-9a-f]{64}$/u.test(sha256)) throw new Error('Некорректный SHA-256 аватара.'); if (!/^[A-Za-z0-9_-]{43}$/u.test(ar)) throw new Error('Некорректный Arweave txId аватара.'); - rows.push(``); + rows.push(``); } if (cleanDescription) rows.push(cleanDescription); return rows.join('\n'); diff --git a/shine-UI/js/services/dm-tech-blocks.js b/shine-UI/js/services/dm-tech-blocks.js index 0a2616cb..10b2008e 100644 --- a/shine-UI/js/services/dm-tech-blocks.js +++ b/shine-UI/js/services/dm-tech-blocks.js @@ -11,6 +11,16 @@ function defaultParsed(rawText = '') { }; } +function hasTechPrefix(text = '', offset = 0) { + return String(text || '').startsWith('`; + return ``; } const cleanReason = String(reason || '').trim().toLowerCase(); - return ``; + return ``; } export function buildDmReplyTechBlock({ baseKey = '' } = {}) { const cleanBaseKey = String(baseKey || '').trim(); if (!cleanBaseKey) return ''; - return ``; + return ``; } export function parseDmTechBlocks(rawText = '') { const text = String(rawText || ''); - if (!text.startsWith('', cursor); if (end < 0) { if (!blocks.length) return defaultParsed(text); break; } - const inner = text.slice(cursor + 7, end); + const prefixLength = getTechPrefixLength(text, cursor); + if (!prefixLength) break; + const inner = text.slice(cursor + prefixLength, end); const segments = inner.split(';').map((part) => String(part || '').trim()).filter(Boolean); if (!segments.length) { if (!blocks.length) return defaultParsed(text);