Yobi.chat

Клиентский API

Интерфейс, через который работают приложения Yobi. Любой может написать собственный клиент.

Общие правила

Каждый метод вызывается запросом POST https://yobi.chat/api/v1/<метод> с JSON-телом. Методы, кроме входа, требуют заголовок Authorization: Bearer <токен сессии>.

Успешный ответ:

{"ok": true, "result": { ... }}

Ошибка:

{"ok": false, "error_code": 400, "error": "PHONE_INVALID", "description": "неверный номер телефона"}

error — машиночитаемый код, по нему клиент решает, что делать. При превышении лимита приходит код 429 FLOOD_WAIT с полем retry_after — через сколько секунд можно повторить.

Ограничение по умолчанию — 30 запросов в секунду на сессию с запасом 120. Даты — unix-время в секундах. Смещения форматирования (entities) считаются в кодовых единицах UTF-16, как индексы строк в JavaScript, Java и Swift.

Вход

  1. auth.sendCode {"phone": "+79001234567"} — отправить код. Если у аккаунта есть активные сессии, код придёт сообщением от служебного аккаунта «Yobi» в приложение (delivery: "app"), иначе по SMS. Повторно — через timeout секунд; force_sms: true — принудительно по SMS.
  2. auth.signIn {"phone", "code"} — ответ содержит одно из трёх:
  • token и user — вход выполнен;
  • need_signup: true — номер новый, вызовите auth.signUp;
  • password_required: true — включён облачный пароль, вызовите auth.checkPassword.
  1. auth.signUp {"phone", "code", "first_name", "last_name"} — регистрация.
  2. auth.checkPassword {"phone", "code", "password"} — вход с облачным паролем.

Заголовок X-Yobi-Device задаёт название устройства в списке сессий, например Yobi для Android, Pixel 8.

МетодПараметрыРезультат
auth.logOut—завершить текущую сессию
auth.getSessions—список Session
auth.terminateSessionsession_id (0 — все, кроме текущей)true
auth.setPasswordcurrent_password, new_password (пусто — отключить), hinttrue
auth.hasPassword—{"has_password": bool}
auth.deleteAccount—true

Обновления в реальном времени

Откройте WebSocket https://yobi.chat/api/v1/ws?token=<токен> (схема ws:// или wss://). Сервер присылает кадры двух видов:

{"pts": 42, "update": {"type": "new_message", ...}}   — сохраняемое событие
{"update": {"type": "typing", ...}}                    — эфемерное событие

У каждого пользователя есть счётчик pts, он растёт на 1 с каждым сохраняемым событием. Клиент хранит последний применённый pts:

  • пришёл кадр с pts = свой + 1 — применить и запомнить;
  • пришёл кадр с pts больше, чем свой + 1 — часть событий пропущена: вызвать updates.getDifference {"pts": свой} и применить полученное;
  • после переподключения первый кадр hello содержит state.pts — если он больше своего, вызвать updates.getDifference.

Если getDifference вернул too_long: true, пропущено слишком много — заново загрузите список чатов (chats.getDialogs). Пока final: false, повторяйте запрос с новым pts.

Клиент может отправлять в сокет:

  • {"type": "ping"} — сервер ответит pong;
  • {"type": "typing", "chat_id": 1, "action": "typing"} — «печатает…»; действия: typing, record_voice, upload_photo, upload_video, upload_document, cancel;
  • {"type": "presence", "active": false} — приложение свёрнуто (пользователь перестаёт быть «в сети»);
  • {"type": "call_signal", "call_id": 1, "data": {...}} — сигнал WebRTC (SDP, ICE) другой стороне звонка.

Типы событий

typeПоляЗначение
new_messagemessage, usersновое сообщение
edit_messagemessageсообщение изменено
delete_messageschat_id, idsсообщения удалены
read_inboxchat_id, max_idвы прочитали чат на другом устройстве
read_outboxchat_id, max_id, reader_idсобеседник прочитал ваши сообщения
chatchatчат изменился (название, права, закреп и т. д.)
chat_removedchat_id, kicked, deletedчат пропал из списка
history_clearedchat_id, max_idистория очищена
reactionschat_id, msg_id, reactionsизменились реакции
typingchat_id, user_id, actionкто-то печатает
user_statususer_id, online, last_seenстатус «в сети»
useruserпрофиль изменился
callback_answerquery_id, text, show_alert, urlответ бота на нажатие кнопки
sessions_changed—список сессий изменился
pollchat_id, msg_id, pollизменились итоги опроса (эфемерное)
stickers_changed, gifs_changed—изменились наборы стикеров / сохранённые GIF
commentschat_id, msg_id, commentsчисло комментариев к посту (эфемерное)
topics_changed, topic_deletedchat_id, topic_idизменились темы форума
read_threadchat_id, thread_id, max_idветка прочитана на другом устройстве
stories_changeduser_id или chat_idу человека, канала или группы изменились истории (эфемерное)
story_mention, story_newstory_id, user_idвас отметили в истории; новая история человека с «Уведомлять о новых историях»
story_collabstory_id, user_id, statusприглашение в совместную историю и ответ на него
story_answer, story_reminderstory_id, area (+user_id)ответ на «Вопрос» (автору); наступил обратный отсчёт («Напомнить»)
story_results, story_reaction, story_commentsstory_id, area / emoji / countизменились итоги стикеров, реакция, комментарии (эфемерные, автору)
story_approval, stories_pendingstory_id, chat_id, approvedрешение по истории в группе; есть истории на одобрение (администраторам)
stories_scheduled_changed, story_drafts_changed, highlights_changed—отложенные, черновики, «Актуальное» изменились на другом устройстве
call_incoming, call_state, call_signalcall, dataзвонки (эфемерные)
secret_chat, secret_messagesecret, messageсекретные чаты (только устройству чата)
search_recent_changed—«Недавние» в поиске изменились на другом устройстве — перезапросите search.getRecent

Пользователи и контакты

МетодПараметрыРезультат
users.getMe—User
users.getUseruser_idUser
users.getUsersids (до 200)словарь id → User
users.updateProfilefirst_name, last_name, bio (любые)User
users.setUsernameusername (пусто — удалить)User
users.checkUsernameusername, chat_id{"available", "description"}
users.setAvatarfile_id (пусто — удалить)User
users.getPrivacy / users.setPrivacylast_seen, phone: everybody, contacts, nobodyнастройки
users.resolveusername{"user"} или {"chat"}
users.resolvePhonephone{"user"}; ссылка на номер yobi.chat/+79991234567, учитывает «Кто может найти меня по номеру»; ошибки PHONE_INVALID, PHONE_NOT_FOUND
users.blockuser_id, blockUser
contacts.get—список User
contacts.adduser_id или phone, first_name, last_nameUser
contacts.deleteuser_idtrue
contacts.importcontacts: [{phone, first_name, last_name}]найденные User
search.globalquery{"users", "chats"}

Чаты

Тип чата: private (личный), group (группа, до 200 000 участников), channel (канал, без ограничений). Роли: owner, admin, member, left, kicked.

МетодПараметрыРезультат
chats.getDialogsarchived, offset, limit{"chats", "users", "archived_count", "state"}
chats.getChatchat_id{"chat", "users"}
chats.getPrivateuser_idличный чат (создаётся при необходимости)
chats.createGrouptitle, user_idsChat
chats.createChanneltitle, about, usernameChat
chats.editchat_id, title, about, username, avatar_file_id, signatures, slow_mode_sec, permissionsChat
chats.addMemberschat_id, user_idsChat
chats.joinchat_idвступить в публичный чат
chats.leavechat_idвыйти
chats.kickchat_id, user_id, banисключить
chats.unbanchat_id, user_idснять блокировку
chats.restrictchat_id, user_id, until (0 — снять)запретить писать
chats.promotechat_id, user_id, rights (null — снять), titleназначить администратора
chats.transferOwnershipchat_id, user_id, passwordпередать владение
chats.getMemberschat_id, filter (admins, banned, bots), query, offset, limit{"members", "users"}
chats.updateDialogchat_id, pinned, archived, muted_until (-1 — навсегда, 0 — включить), draftChat
chats.deletechat_id, for_allудалить чат
chats.clearHistorychat_idочистить у себя
chats.exportInvitechat_id, renewосновная ссылка-приглашение
chats.createInvitechat_id, title, expires_at, usage_limitдополнительная ссылка
chats.getInviteschat_idсписок ссылок
chats.revokeInvitecodeотозвать
chats.checkInvitecodeчто внутри ссылки
chats.joinByInvitecodeвступить по ссылке

Права администратора (rights): can_post, can_edit_others (каналы), can_delete, can_ban, can_invite, can_pin, can_change_info, can_promote.

Сообщения

МетодПараметрыРезультат
messages.getHistorychat_id, offset_id, direction (before, after, around), limit{"messages", "users", "replies"}
messages.sendchat_id, text, entities или parse_mode, file_id, media_kind, reply_to_id, silent, schedule_atMessage
messages.editchat_id, msg_id, text, entitiesMessage
messages.deletechat_id, ids, for_alltrue
messages.forwardfrom_chat_id, ids, to_chat_id, hide_authorсписок Message
messages.reactchat_id, msg_id, emoji (пусто — снять)Message
messages.pinchat_id, msg_id (0 — открепить), silenttrue
messages.readchat_id, max_idtrue
messages.typingchat_id, actiontrue
messages.searchchat_id (0 — везде), query, filter (media, files, voice, audio, links), offset_id, limit (до 100)страница сообщений (от новых к старым); в одном чате на первой странице (без offset_id) — ещё count: сколько всего найдено (для «N из M» в просмотрщике медиа)
messages.pressButtonchat_id, msg_id, data{"query_id"}
messages.getScheduled / messages.deleteScheduledchat_id / idотложенные
messages.reportchat_id, msg_id, user_id, reason, commentжалоба

Типы форматирования (entities[].type): bold, italic, underline, strikethrough, spoiler, code, pre (+language), blockquote, text_link (+url). Ссылки, @упоминания, #хэштеги и /команды сервер распознаёт сам. В превью вне пузыря (список чатов, закреп, цитата ответа, поиск, уведомления) клиент закрывает каждый spoiler отрезком ▒ той же длины в символах (не больше 12) и не даёт раскрыть; сервер так же маскирует текст пушей.

Файлы

Загрузка — POST https://yobi.chat/api/v1/upload, multipart/form-data:

  • file — содержимое;
  • kind — необязательно: photo, video, voice, audio, document, sticker, animation, video_note;

для sticker — ещё sticker_target (regular, mask, custom_emoji, icon): сервер проверяет TGS или приводит картинку к PNG 512 px (эмодзи и обложка — 100×100);

  • duration, width, height — для медиа. У видео (MP4/MOV/WebM) сервер сам читает размеры кадра (с учётом поворота) из файла (они важнее присланных), а длительность — если её не прислали.

Ответ — объект Media с file_id. Его передают в messages.send. Скачать файл: GET https://yobi.chat/files/<file_id>, поддерживается Range для видео и аудио, ?download=1 — скачать как вложение.

Новые возможности

МетодПараметрыРезультат
messages.send с pollpoll: {question, options[], anonymous, multiple, quiz, correct_option, explanation, open_period}сообщение-опрос
polls.vote, polls.close, polls.getVoterschat_id, msg_id, options[]сообщение / голоса
stickers.getInstalled {type?}, getRecent, getSet, install, search, reorderSets {type?, names}type: regular (по умолчанию), mask, custom_emoji, gifустановленные наборы стикеров, масок, эмодзи и GIF
stickers.createSet {title, name, set_type, format, stickers[{file_id, emoji, keywords?, mask_position?}], thumb_file_id?}, addToSet, replaceSticker, setStickerEmoji, setMaskPosition, setPosition, removeFromSet, setThumb, renameSet, deleteSet, getMine {type?}, checkNamename — короткое имя (общее для всех типов)свои наборы (то же умеет бот @Stickers)
stickers.getCustomEmoji {ids}, stickers.searchCustomEmoji {emoji}до 200 idсвои эмодзи: entity custom_emoji с custom_emoji_id
stickers.getStats, getTop, getSetStats, getSetTop, getTopInSet, getUsageTopperiod: d7, d30, totalстатистика своих наборов
stickers.getEmojiCategories, getEmojiKeywords {lang?}, searchByEmoji {emoji[], type?, query?, limit?}, searchSets {query, type?, limit?}lang: ru (по умолчанию) или enпанель эмодзи/стикеров/GIF: категории, ключевые слова, поиск
stickers.setAccess {name, access, user_ids?, chat_ids?}, stickers.resolveAccessTarget {query}access: all или selectedдоступ к своему набору «Для всех» / «Для выбранных»
gifs.getSaved, gifs.save, gifs.search, gifs.importfile_id, unsave, query, urlGIF (поиск — при YOBI_GIF_PROVIDER)
chats.edit с is_forumвключить темы в группе
topics.get, topics.create, topics.edit, topics.deletechat_id, topic_id, title, icon_emoji, closed, pinnedтемы форума
messages.getHistory / messages.read / messages.send с thread_idработа с темой или веткой комментариев
chats.setDiscussionchannel_id, group_idгруппа обсуждения канала
messages.getDiscussionchat_id, msg_idгруппа и ветка комментариев к посту
stories.post, getFeed, getUser, view, react, getViewers, edit, delete, getCloseFriends, setCloseFriendsfile_id, caption, privacy, allowed, period_hours, pinnedистории (полный список — раздел «Истории» ниже)
messages.send с reply_story_idответ на историю
inline.query, inline.sendbot_username, query, query_id, result_idинлайн-боты
webapp.init, webapp.sendDatabot_id, chat_id, url, source, start_paramзапуск мини-приложения (только у запущенного бота — см. «Мини-приложения: сначала «Запустить»»)
payments.getForm, payments.paychat_id, msg_id, order_info, tip_amountоплата счёта
calls.getConfig, calls.request, calls.accept, calls.hangup, calls.signal, calls.getHistoryuser_id, video, call_idзвонки
secret.request, secret.accept, secret.discard, secret.list, secret.send, secret.getInbox, secret.ackuser_id, g_a/g_b (P-256, base64), payloadсекретные чаты

Версия 3: сообщения, чаты, аккаунт

Новые поля messages.send: geo {lat, lon, accuracy, title, address, live_period}, contact {phone, first_name, last_name, user_id}, checklist {title, items[], others_can_add, others_can_mark}, has_spoiler, view_once, quote {text, offset}, reply_to_chat_id, effect, no_link_preview, send_as, repeat_sec, when_online.

МетодыНазначение
messages.sendAlbumальбом: items[{file_id, has_spoiler, text}]
messages.openViewOnce, messages.toggleChecklist, messages.addChecklistItems, messages.editLiveLocationодноразовые медиа, списки задач, трансляция геопозиции
messages.getPinned, messages.unpin, messages.unpinAllнесколько закреплённых сообщений
messages.getReadDate, messages.getByDate, messages.searchPublic, messages.getLinkPreviewкогда прочитано, переход к дате, поиск по публичным каналам, превью ссылки
messages.getGameUrl, messages.getGameScores, messages.sendQuickReplyигры ботов, быстрые ответы
polls.addOptionвариант от участника
chats.setTTL, chats.setNoForwards, chats.setWallpaperавтоудаление, запрет пересылки, обои
chats.getAdminLog, chats.getStats, chats.getMessageStats, chats.getSimilarжурнал действий, статистика, похожие каналы
chats.getJoinRequests, chats.resolveJoinRequest, chats.createInviteEx, chats.editInvite, chats.deleteRevokedInvites, chats.getInviteJoinersзаявки и ссылки-приглашения
chats.ban, chats.restrictMember, chats.setMemberTag, chats.getMemberTags, chats.getSendAsограничения, метки участников, отправка от имени канала
folders.get, save, delete, reorder, share, editInvite, getInvites, deleteInvite, checkInvite, joinInvite, hideInviteUpdatesпапки (значок, цвет тега, типы и списки чатов), порядок вкладок (0 — «Все чаты»), ссылки /addlist/<code> с обновлениями; события folders_changed, folder_settings_changed; настройки вкладок — account.setSettings (folder_tags, folder_tabs_position, folder_tabs_style, folder_all_chats_index)
privacy.getRules, privacy.setRule, users.getBlockedприватность с исключениями
users.updateProfileExtra, users.getPhotos, users.setMainPhoto, users.deletePhoto, contacts.setNote, contacts.getBirthdaysдень рождения, эмодзи-статус, цвет, личный канал, фото, заметки; contacts.getBirthdays {tz_offset} — дни рождения контактов сегодня и в ближайшие 7 дней, «сегодня» по часовому поясу клиента (секунды к востоку от UTC)
account.getSettings, setSettings, getNotifySettings, setNotifySettings, setEmail, verifyEmail, registerPush, unregisterPushнастройки, почта, push (kind: fcm, apns, unifiedpush)
auth.exportLoginToken, checkLoginToken, acceptLoginTokenвход по QR-коду
auth.requestPasswordRecovery, auth.recoverPasswordсброс облачного пароля через почту
auth.passkeyOptions, auth.passkeyLogin, passkeys.registerOptions, register, list, deleteключи доступа (WebAuthn)
groupCalls.create, start, subscribe, join, leave, signal, setState, moderate, edit, end, getMembers, reactвидеочаты и трансляции (mesh WebRTC)
business.get, set, getQuickReplies, saveQuickReply, deleteQuickReply, getLinks, saveLink, deleteLink, resolveLink, connectBot, disconnectBotбизнес-профиль
stickers.getFavorites, stickers.fave, stories.getChat, stories.getAlbums, stories.setAlbum, apps.getCatalog, apps.getAttachMenuизбранные стикеры, истории каналов и альбомы, каталог мини-приложений

Оформление чатов (миграция 021):

  • chats.setWallpaper {chat_id, wallpaper, for_both} — wallpaper: {kind: color|gradient|image|theme, color, color2 (#rrggbb), file_id (для image), dark_dim (0..1), blur, pattern, theme}, {} — сбросить; url картинки сервер вычисляет сам. В личном чате с человеком — для себя или для обоих; в группе и канале обои общие, их меняют только владелец и администраторы с can_change_info (иначе 403 CHAT_ADMIN_REQUIRED); в чате с ботом — всегда 403 CHAT_ADMIN_REQUIRED. Изменение приходит всем событием chat.
  • chat.wallpaper_locked — зритель не может менять обои (скрыть пункт «Обои»). chat.accent_color — цвет названия группы или канала, индекс палитры #E5544B, #F08A24, #D9A400, #3FB15D, #14A3A3, #2E7BF0, #7F5AF0, #C94FC9, #E0529C, #6B7A8F; нет поля — цвет по id. Меняется через chats.edit {accent_color} (-1 — сбросить).
  • bots.setWallpaper {bot_id, wallpaper} → {wallpaper} — владелец бота задаёт обои, которые видят все собеседники бота; bots.getMyBots возвращает wallpaper.
  • chats.getManageCounts {chat_id} → {admins, members, banned, invites, join_requests, reactions} — для экрана «Управление каналом/группой» (администраторам).
  • В канал нельзя отправить geo, contact и checklist: 400 MEDIA_NOT_ALLOWED.

Видеочат: новичок шлёт SDP-предложение каждому участнику через groupCalls.signal (data: {type: offer|answer|ice, …}), события приходят как group_call, group_call_member, group_call_signal, group_call_ended.

Версия 4: настройки

МетодыНазначение
account.sendChangePhoneCode, account.changePhoneсмена номера: код по SMS на новый номер
account.setSettings с hide_read_time, find_by_phoneскрыть время прочтения; кто может найти по номеру (everybody, contacts)
account.resetNotifySettingsсбросить все настройки уведомлений и исключения
account.registerPush с kind: webpushподписка браузера (JSON PushSubscription); ключ VAPID — account.getPushConfig().webpush_key
contacts.deleteImportedудалить контакты, загруженные из телефонной книги
messages.clearAllDraftsудалить все черновики
messages.getScheduled, getScheduledCounts, editScheduled, sendScheduledNow, deleteScheduledотложенные сообщения
stickers.reorderSetsпорядок наборов стикеров

Умный поиск

Постраничные методы принимают offset (строка из next_offset прошлой страницы; в первый раз — пусто) и возвращают next_offset (нет поля — больше ничего нет). Страница сообщений: {"messages", "users", "chats", "next_offset"}, новые сверху.

МетодПараметрыРезультат
search.getRecentlimit (20, до 50){"recent": [{"user_id" или "chat_id", "date"}], "users", "chats", "apps"}; apps — {bot_id: BotApp} для ботов с мини-приложением (кнопка «Открыть», monthly_users)
search.addRecent / search.removeRecentuser_id или chat_idtrue; хранится 50 последних, общие для всех устройств
search.clearRecent—true
messages.searchGlobalquery, filter, offset, limit (40, до 100)сообщения из всех чатов, где вы участник (без секретных, удалённых у себя и очищенной истории). filter: "" (текст, query обязателен), photo, video (с GIF), photo_video, document, link, music, voice (с кружками). С фильтром query необязателен и ищется по подписи, имени файла, адресу и заголовку ссылки
search.postsquery (от 2 символов), offset, limit (30, до 50)посты публичных каналов — глобальный поиск, бесплатно для всех
search.channelslimit (20, до 50){"my_channels", "recommended"}: свои каналы (свежие сверху) и популярные публичные, на которые вы не подписаны
search.appsquery, offset, limit (20, до 50){"apps": [BotApp], "users", "next_offset"}; BotApp: bot_id, url, users_count, monthly_users (открывали за 30 дней), featured

Истории

Формат истории (холст 9:16, kind media/text/collage, фон, коллаж, правки, слои, музыка, голос, интерактивные области и их results) — единый контракт в docs/stories.md репозитория; там же все уточнения сервера. Старые клиенты, которые шлют только file_id, caption, areas, работают как раньше. Ошибки проверки содержимого — STORY_CONTENT_INVALID, STORY_AREAS_INVALID, STORY_MEDIA_INVALID, STORY_VIDEO_TOO_LONG, FILE_ID_INVALID.

МетодыНазначение
stories.post, postMany {items ≤ 10}, edit, delete, repost {story_id / chat_id+msg_id}публикация: новые поля kind, background, collage, edits, layers, music, voice, noforwards, reposts_allowed, view_once, excluded (privacy=except), collab_user_id, scheduled_at, repost, chain_id, comments_enabled, group_id, period_hours до 168
stories.getFeed, getUser, getChat, getByIds, view (→ {expired_ids}), react, track {event: complete/exit/link/sticker}лента (close_friends у автора, stealth_until), просмотр, одноразовые истории
stories.vote, slide, answer, getAnswers, remind, going, getVotersинтерактивные стикеры: опрос, викторина, слайдер, вопрос, обратный отсчёт, событие
stories.getViewers {filter: all/contacts/reactions/voted, query, offset}, getStats, getSeriesStats {date}зрители (с ответами на стикеры), статистика и удержание серии дня
stories.collabRespond, getChain, searchHashtag, getMap {lat, lon, radius_km}совместные истории, «Добавь своё», хештеги, карта историй
stories.getScheduled, cancelScheduled, getDrafts, saveDraft, deleteDraftотложенные и черновики (на всех устройствах)
stories.getHighlights, getHighlightStories, saveHighlight, deleteHighlight, reorderHighlights, getArchive {month}, getMemories«Актуальное» (старые getAlbums/setAlbum работают поверх него), календарь архива, «В этот день»
stories.hideAuthor, getHidden, setStealth, setNotify, getNotifyскрыть автора из ленты, незаметный просмотр (25 минут), «Уведомлять о новых историях»
stories.getComments, addComment, deleteCommentкомментарии к историям каналов
chats.edit {stories_mode: off/on/approval}, stories.getPending, stories.approveистории участников в группах и их одобрение
stories.report, messages.report {story_id}; админка: admin.storyAction {story_id, action: delete/hide_map/show_map}жалобы на истории (chat_type: story в очереди, решение hide_map)
bots.allowStories, bots.getStoryBots, apps.shareToStory {bot_id, file_id, text, link}; Bot API postStory, deleteStoryистории от ботов с разрешения пользователя; мини-приложение создаёт черновик

Секретные чаты

Каждое устройство создаёт пару ключей ECDH P-256 и передаёт открытый ключ (несжатая точка, 65 байт, base64). Общий ключ: ECDH → HKDF-SHA256(salt="yobi-secret-<id>", info="message-key") → AES-256-GCM. payload — base64(iv 12 байт ‖ шифротекст) от JSON {"v":1,"type":"msg"|"read"|"delete"|"ttl","rid":"…","date":…,"text":"…","media":{…},"ttl":…}. Файлы шифруются отдельным случайным ключом AES-GCM и загружаются как secret.bin; ключ и iv передаются внутри сообщения. Сервер удаляет сообщения после secret.ack.

Звонки

calls.request → собеседнику call_incoming → calls.accept → звонящий создаёт SDP-предложение, стороны обмениваются call_signal ({"sdp": …} и {"candidate": …}). STUN/TURN — из calls.getConfig (TURN включается переменными YOBI_TURN_URLS и YOBI_TURN_SECRET для coturn с use-auth-secret).

Мини-приложения: сначала «Запустить»

Мини-приложение открывается только у бота, которого пользователь запустил сам. Запущен — у пользователя есть личный чат с ботом (он не вышел из него), в этом чате есть хотя бы одно сообщение от пользователя (/start из bots.start или любое другое — старые диалоги считаются запущенными) и пользователь не заблокировал бота. Сообщения от бота (уведомления, например от @Usernames) запуском не считаются. Исключений нет — владелец бота тоже нажимает «Запустить».

Проверка на сервере: webapp.init, activities.launch (и activities.invite / activities.answer accept) и вход мини-приложения @Usernames по заголовку X-Yobi-Init-Data отвечают ошибкой 403 BOT_NOT_STARTED, data: {bot_id, username}, если бот не запущен (уже выданные initData перестают работать, если пользователь удалил чат или заблокировал бота).

webapp.init {bot_id, chat_id, url, source, start_param?} → {url, init_data, query_id?, bot_id}. start_param — из ссылки <адрес>/<бот>?startapp=<параметр> (латиница, цифры, _, -, до 64 символов, иначе START_PARAM_INVALID); попадает в подписанные initData полем start_param. Успешный webapp.init засчитывается как запуск приложения в статистике бота; apps.logLaunch клиент вызывает после того, как приложение действительно открылось.

Поведение клиента (все входы: кнопки под сообщением и клавиатуры, кнопка меню, «скрепка», Apps «Открыть», «Открыть» в списке чатов, страница приложения, поиск, ссылки, поля имени в профиле/канале/группе, «Юзернеймы» в настройках):

  1. Перед открытием закрыть окна, настройки и экран управления чатом — мини-приложение не должно оказаться под ними.
  2. Вызвать webapp.init (или activities.launch). Успех — открыть приложение.
  3. BOT_NOT_STARTED — не открывать приложение и не отправлять /start за пользователя: открыть личный чат с ботом (chats.getPrivate), показать внизу кнопку «Запустить» и над ней подсказку «Нажмите «Запустить», чтобы открыть «<название>»»; запомнить запрошенный запуск (url, source, chat_id, start_param, сессию игры, название).
  4. Пользователь нажимает «Запустить» → bots.start {bot_id} (сообщение /start от пользователя, чат появляется в списке) → повторить шаг 2 с запомненными параметрами — приложение откроется само. Переход в другой чат отменяет ожидание.
  5. Ссылка <адрес>/<бот>?start=<параметр> открывает чат с ботом и кнопкой «Запустить», которая отправляет bots.start {bot_id, param}; ?startapp[=<параметр>] — главное мини-приложение бота по шагам 1–4.

Статьи

Сообщение kind="article" — документ из блоков, как «Статья» в Telegram. Отправка: messages.send {chat_id, article} (без text, file_id, poll и других вложений); правка автором или администратором канала с правом изменять чужие посты: messages.edit {chat_id, msg_id, article} — статья заменяется целиком, text для статьи не принимается. Пересылка сохраняет статью. В сообщении приходит поле article, а text равен заголовку; превью в списке чатов и уведомлениях — «📰 заголовок». Поиск (messages.search, messages.searchPublic) ищет и по тексту статьи.

article: title (до 256 символов; если пуст, заголовком становится первый блок heading), cover {file_id}, blocks[]; сервер добавляет excerpt (до 300 символов для карточки), read_minutes и photo (объект Media) у обложки и картинок. Блок: type и поля по типу — heading {level 1–3, text, entities}, paragraph/quote/pullquote/small {text, entities}, code {text, language}, divider, list {ordered, items[{text, entities}]}, checklist {items[{text, entities, checked}]}, details {text, entities, open, blocks[]} (до 3 уровней), table {header, rows[[{text, entities}]]} (до 100×20), image {file_id, text, entities} (подпись), formula {tex}. Текст — как в сообщениях (смещения UTF-16); дополнительно разрешены entities subscript, superscript, marked. Картинки — file_id из /api/v1/upload (фото или GIF). Ограничения: 100 КБ JSON, 50 картинок, 1000 блоков, 10000 символов в блоке. Ошибки: ARTICLE_INVALID, ARTICLE_TOO_LONG, FILE_ID_INVALID.

Объекты

User: id, first_name, last_name, username, bio, phone (если видно по приватности), avatar, is_bot, is_system, is_contact, is_self, is_blocked, status (online, offline, recently, long_ago, bot, service), last_seen, bot_commands, bot_menu_button, bot_main_app_url (главное мини-приложение бота; пусто — нет), bot_list_button (показывать кнопку «Открыть» в строке бота в списке чатов: есть главное мини-приложение и владелец не выключил её; видна только при Chat.bot_started; нажатие открывает bot_main_app_url через webapp.init, apps.logLaunch source="chatlist").

Chat: id, type, title, about, username, avatar, peer_id, member_count, my_role, rights, permissions, last_message, unread_count, unread_mentions, read_inbox_max, read_outbox_max, pinned_msg_id, muted_until, pinned, archived, draft, slow_mode_sec, signatures, sort_date, bot_started (личный чат с ботом: зритель уже запустил бота; кнопку «Открыть» из bot_list_button показывать только при bot_started, пока бот не запущен — «Запустить» в чате; меняется событием chat после bots.start, первого сообщения боту и блокировки/разблокировки).

Message: chat_id, id (растёт внутри чата), sender_id, date, kind (text или service), text, entities, media, reply_to_id, forward, reply_markup, author_signature, service, views, edit_date, reactions, out.

Media: file_id, kind, name, mime, size, width, height, duration, url.

Ссылки

  • https://yobi.chat/<username> — профиль, канал или группа;
  • https://yobi.chat/<username>/<id> — сообщение в публичном чате;
  • https://yobi.chat/+<код> — ссылка-приглашение;
  • https://yobi.chat/addlist/<код> — папка с чатами;
  • https://yobi.chat/m/<код> — бизнес-ссылка на чат с готовым текстом;
  • https://yobi.chat/<бот>?start=<параметр> — чат с ботом, «Запустить» отправит /start <параметр>; https://yobi.chat/<бот>?startapp=<параметр> — главное мини-приложение бота (start_param), у незапущенного бота — сначала «Запустить»;
  • yobi://login?token=<токен> — вход по QR-коду.
  • yobi://resolve?phone=79991234567 и https://yobi.chat/+79991234567 — профиль по номеру телефона (QR-код профиля без имени пользователя).