Skip to content

Releases

v1.4.0

Choose a tag to compare

@SecurityTrip SecurityTrip released this 30 Oct 19:12
086af2a

The current rules of Max, mini app backends, tools for writing bots, and production operations:
health, tracing, a WebFlux webhook, native image and non-blocking deduplication.

Added

  • Spring Boot starter: the health contributor max when Spring Boot Actuator is present
    (MaxHealthAutoConfiguration). Its part bot (MaxBotHealthIndicator) is DOWN when long polling
    ended (with the error in pollingError), when webhook mode has no endpoint or the subscription to
    max.bot.webhook.url is missing, and when the API does not answer or rejects the token; the request
    to the API (getSubscriptions or getMyInfo) is made at most once per max.bot.health.cache-ttl
    (30 seconds) and waits up to max.bot.health.timeout (5 seconds). Its part storage checks the
    database or Redis of max.bot.storage=jdbc|redis. management.health.max.enabled=false turns it off.

  • New module maxapi-opentelemetry: MaxTracing records API requests as client spans
    (GET /chats/{id} with http.request.method, url.template, http.response.status_code,
    server.address, server.port, http.request.resend_count, error.type), dispatched updates as
    consumer spans (process message_created with messaging.system=max, max.update.type,
    max.chat.id, max.update.handled, error.type) and renewed webhook subscriptions as
    max.webhook.resubscribe. The span of an update is current in the coroutines of its handler, so
    the requests the handler makes are its children; a webhook update handled synchronously continues
    the trace of the server span. The starter creates MaxTracing when the application has an
    OpenTelemetry bean (MaxTracingAutoConfiguration).

  • MaxEventListener.updateContext(update): coroutine context elements the bot adds while it
    dispatches an update, e.g. an OpenTelemetry context or an SLF4J MDCContext; composite combines
    the contexts of its listeners. The onUpdate event of the update is called within that context.

  • Spring Boot starter: the webhook endpoint in reactive web applications (Spring WebFlux), with the
    same answers as the servlet one: 200, 401, 400 and 503. Handlers run on Dispatchers.Default, and
    with max.bot.webhook.async=false the response waits for them without blocking a thread.

  • GraalVM native image: maxapi has reflection metadata for all model classes, the bundled root CA
    and the Kotlin built-ins in META-INF/native-image/io.github.securitytrip/maxapi; the starter
    registers the same hints with Spring AOT (MaxRuntimeHints in META-INF/spring/aot.factories).

  • SuspendUpdateDeduplicator with suspend fun markSeen(update), a non-blocking variant of
    UpdateDeduplicator, set with BotConfig.suspendDeduplicator (also in the builder). The bot calls
    markSeen of a deduplicator that implements both interfaces. The starter passes a
    SuspendUpdateDeduplicator bean to the bot when there is no UpdateDeduplicator bean.

  • MaxBot.isPolling and MaxBot.pollingFailure: whether long polling runs, and the error that ended it.

  • Mini app backends, in the new package ru.securitytrip.maxapi.webapp of maxapi with no new
    dependencies. WebAppData.validate(initData, botToken, maxAge) checks window.WebApp.initData
    as https://dev.max.ru/docs/webapps/validation describes (values URL-decoded once, exactly one
    hash, HMAC_SHA256(HMAC_SHA256("WebAppData", botToken), sorted key=value lines), auth_date
    no older than maxAge, one hour by default) and returns the user, the chat, query_id,
    auth_date, start_param, ip and all other fields; it throws WebAppDataException with a
    reason: MALFORMED, MISSING_HASH, DUPLICATE_HASH, INVALID_SIGNATURE or EXPIRED.
    WebAppData.verifySignature only checks the signature and returns a Boolean.

  • WebAppPhone.verify checks the phone number from requestContact() of MAX Bridge
    (HMAC_SHA256(botToken, "authDate=…\nphone=…\nuserId=…")), and ContactAttachment.verify (also on
    ContactAttachmentPayload) the contact shared with a request_contact button
    (HMAC_SHA256(botToken, vcf_info); checked against the live API). All hashes are compared in
    constant time.

  • MaxLinks builds deep links: bot, botStart (?start=, 1–128 characters), miniApp
    (?startapp=, up to 512 characters of [A-Za-z0-9_-]) and share (:share?text=), and throws
    IllegalArgumentException for a payload Max would drop. MaxLimits.BOT_START_PAYLOAD_LENGTH,
    MaxLimits.isValidOpenAppPayload and MaxLimits.checkOpenAppPayload are public.

  • Spring Boot starter: controller parameters annotated with @MaxWebAppUser get the checked
    WebAppUser or WebAppData, and requests without valid initData get 401. max.bot.web-app.source
    says where initData is (header by default, parameter or body), with max.bot.web-app.header
    (X-Max-Init-Data), max.bot.web-app.parameter (initData) and max.bot.web-app.max-age (1h).

  • maxapi-ktor: the MaxWebApp route plugin answers 401 to calls without valid initData (from the
    X-Max-Init-Data header by default), and call.maxInitData() and call.maxWebAppUser() return
    the checked data.

  • examples-spring has a mini app backend with a page that calls it, an open_app button, a
    startapp link and a verified request_contact contact.

  • MaxLimits holds the limits of Max, and MaxClient checks them before sending: text of a message
    or a comment up to 4000 characters; keyboards of up to 30 rows, 1 to 210 buttons and 7 buttons in
    a row (3 in a row with a link, open_app, request_geo_location or request_contact button,
    as the live API enforces it); button text
    of 1–128 characters, callback and clipboard payloads up to 1024, link URLs up to 2048,
    open_app payloads up to 512 of [A-Za-z0-9_-]; up to 32 bot commands with names of 1–64 and
    descriptions of 1–128 characters. sendMessage, editMessage, answerOnCallback, sendComment,
    editComment, editMyCommands and MaxBot.syncCommands throw IllegalArgumentException with
    the broken limit instead of making a request. Lengths are counted in UTF-16 code units, as the
    server counts them (checked against the live API: an emoji takes 2 characters).

  • MaxClient.sendLongMessage (with sendLongMessageToChat and sendLongMessageToUser, in the
    blocking and async facades too) and UpdateContext.replyLong (replyLongBlocking) send text
    longer than 4000 characters as several messages; the keyboard and other attachments go with the
    last one. TextSplitter does the splitting: between paragraphs, then lines, then words, never
    inside a surrogate pair, and with Markdown and HTML markup closed and opened again at every cut.

  • RateLimit.MAX: the limits of Max, 30 requests per second and 2 answers to button presses per
    second in a chat. RateLimit.withPerChatAnswers sets the per-chat limit of answers in any
    RateLimit; MaxBot tells the client the chat of every press it dispatches, because
    POST /answers carries only the callback_id.

  • MaxBot.ensureWebhook (and ensureWebhookBlocking) subscribes to a webhook again if Max dropped
    the subscription after 8 hours without successful deliveries, and reports it to the new
    MaxEventListener.onWebhookResubscribe. maxapi-micrometer counts it in
    max.bot.webhook.resubscriptions.

  • Spring Boot starter: max.bot.rate-limit=none|max (default none) sets RateLimit.MAX, and in
    webhook mode with max.bot.webhook.url the bot checks its subscription every
    max.bot.webhook.check-interval (default 10 minutes, 0 turns it off) and subscribes again when
    it is gone.

  • Scenes: scene("name") { ... } (Scene.builder in Java) describes a wizard on top of the dialog
    state, so it works with any StateStorage. Steps have a prompt, checks (validate, with an error
    text or an onInvalid reaction) and an optional input that reads other kinds of answers; answers
    are kept in DialogState.data under the step names. A scene is entered with enterOnCommand,
    enterOnCallback or Scene.enter(ctx, data); /back or backButton returns a step, /cancel or
    cancelButton runs onCancel, and option(text, value) buttons answer only the current step.
    BaseRouter.include(scene) adds it to a bot; SceneContext gives prompts and callbacks the handler
    context with answers. The QuestionnaireBot example is rewritten on a scene, with a BotTester
    test.

  • Keyboards: Paginator shows a long list page by page with a ◀ 2/7 ▶ row packed by CallbackData
    (page and argument), and Confirmation is a "Yes / No" question with yes() and no() filters.
    Both keep to the keyboard limits of Max: 7 buttons in a row, 3 with a wide button, 30 rows.

  • Translations picks texts by UpdateContext.locale with a fallback language, from maps or
    ResourceBundles (Translations.fromBundles), with {0} and {name} placeholders and plural
    forms (one/few/many for Russian, Ukrainian and Belarusian, one/other otherwise).

  • Chat registry: Max has no method that lists the chats of a bot, so with BotConfig.chatStore set
    the bot remembers them from bot_added, bot_removed, bot_started, bot_stopped,
    chat_title_changed and the first message from an unknown chat, and MaxBot.chats(vararg types)
    (chatsBlocking) lists the active ones as KnownChat. Records keep the time of their event, so a
    repeated or late delivery never undoes a newer one. Stores: InMemoryChatStore,
    BlockingChatStore for Java, JdbcChatStore in maxapi-jdbc and RedisChatStore in
    maxapi-redis. These update types are requested even without handlers for them.

  • Broadcasts: Broadcaster.send(id, targets, message) (sendBlocking) sends a message, the same or
    per target, to users and chats (BroadcastTarget) at up to 25 requests per second by default,
    waits for Retry-After on 429, records 403 and 404 as unavailable without retrying, and retur...

Read more

v1.3.0

Choose a tag to compare

@SecurityTrip SecurityTrip released this 26 Oct 19:57
0b5d580

Storage for production: dialog states, the polling marker and seen updates in SQL or Redis.

Added

  • maxapi-jdbc: JdbcStateStorage, JdbcMarkerStore and JdbcUpdateDeduplicator keep dialog
    states, the polling marker and the keys of handled updates in any SQL database through a
    DataSource. Only standard SQL is used; createTable() creates a missing table.
  • maxapi-redis: RedisStateStorage (with an optional TTL), RedisMarkerStore and
    RedisUpdateDeduplicator on Lettuce.
  • Spring Boot starter: max.bot.storage=jdbc|redis with max.bot.jdbc.* and max.bot.redis.*
    creates those stores, and StateStorage, MarkerStore and UpdateDeduplicator beans are used by
    the auto-configured bot. The bot settings are now a BotConfig bean (maxBotConfig).
  • Both modules are in maxapi-bom.

v1.2.0

Choose a tag to compare

@SecurityTrip SecurityTrip released this 26 Oct 17:25
72321dc

Fixes from running the examples against the real API.

Added

  • MaxBot.deleteWebhook() (and deleteWebhookBlocking()) removes every webhook subscription of
    the bot.

Fixed

  • MessageContext.text is null for photos, stickers and other messages without text. Max sends an
    empty string for them, so text ?: "..." replied with an empty message (rejected with
    400 proto.payload) and Filters.hasText() let such messages through.
  • Long polling no longer receives nothing while the bot is subscribed to a webhook: Max answers
    getUpdates with empty pages then, so startPolling() now checks the subscriptions first and
    throws MaxException, as documented.
  • sendMessage rejects a message without text, attachments or a forwarded message with
    IllegalArgumentException instead of sending it and getting 400 proto.payload: errors.required.
  • The Ktor example unsubscribes from the webhook when it stops and logs through slf4j-simple.
  • The echo examples change the message on "Like" too, since the web client of Max does not show
    callback notifications.

Changed

  • Documented that the web client of Max shows neither the command menu nor callback notifications.

v1.1.0

Choose a tag to compare

@SecurityTrip SecurityTrip released this 26 Oct 15:54
1d27657

Added

  • MaxTls and MaxClientConfig.trustRussianRootCa (max.bot.trust-russian-root-ca in the Spring Boot
    starter): the library bundles the Russian Trusted Root CA, which the certificate of
    platform-api2.max.ru is issued under, and its own HTTP client trusts it in addition to the JVM's
    certificates. Connecting no longer needs a custom trust store.

v1.0.0

Choose a tag to compare

@SecurityTrip SecurityTrip released this 24 Oct 11:15
8f81acc

The public API is stable from this version on.

Added

  • MIGRATION.md with the changes since 0.x, and a compatibility policy in README.
  • CONTRIBUTING.md.
  • Module descriptions in the API reference for the starter, Ktor, test kit and Micrometer modules.

v1.0.0-rc1

v1.0.0-rc1 Pre-release
Pre-release

Choose a tag to compare

@SecurityTrip SecurityTrip released this 24 Oct 11:05
8f81acc

The public API is stable from this version on.

Added

  • MIGRATION.md with the changes since 0.x, and a compatibility policy in README.
  • CONTRIBUTING.md.
  • Module descriptions in the API reference for the starter, Ktor, test kit and Micrometer modules.

v0.1.0-rc1

v0.1.0-rc1 Pre-release
Pre-release

Choose a tag to compare

@SecurityTrip SecurityTrip released this 23 Oct 23:16