0.10.2

Fixed

  • TDLib internal diagnostics (code 0, e.g. Error: 0: Ping timeout expired) are no longer forwarded to the app as a fake Object.Error — they log at debug. Real Telegram API errors (400/401/…) are unaffected. This stops harmless TDLib noise from surfacing as application errors.
  • Sexy.TDL.Backend error parsing captures the full message instead of truncating at the first non-[A-Z0-9_] character (Ping timeout expired was becoming P).
  • Sexy.Bot.Poller getUpdates failures log with a Sexy.Bot.Poller | getUpdates failed: prefix instead of a bare [error] :timeout, so it's clear what timed out.

0.10.1

Completes two 0.10.0 fixes flagged by fix-verification, plus test coverage and a strict CI.

Fixed

  • notify/3 now retries the button attach once on a 429 (via shared Api.with_429_retry/1), matching the send path; permanent errors (BUTTON_DATA_INVALID) are not retried.
  • mix sexy.tdl.generate_types validates type/field names as identifiers, closing a code-injection vector that doc escaping alone left open.

Changed

Internal

  • Regression tests for Sexy.Bot.Config (config erased on stop; offset ref survives a restart), boot validation, and Sexy.TDL.Backend line parsing.
  • Strict CI: mix format --check-formatted (fails instead of auto-formatting and pushing), credo --strict, dialyzer, deps.unlock --check-unused, hex.audit, and a test matrix on the Elixir floor (1.17) and current (1.19).

0.10.0

Reliability release: 37 audited defects fixed. Breaking changes — see UPGRADING.md for the per-call migration guide.

Breaking

Fixed

  • Poller: a malformed update (e.g. callback_query without data) crash-looped the poller and took down the consumer's supervision tree via offset-0 replay. All update parsing now runs in supervised tasks; the offset survives restarts (atomics); the loop is driven by send_after and ignores stray messages instead of crashing (or stalling — the old GenServer-timeout loop was cancelled by any message).
  • Api: all methods (not just get_updates) return the documented error map on non-JSON responses instead of raising Jason.DecodeError; transport-error descriptions no longer crash on tuple reasons.
  • Payments: optional Session callbacks are resolved reliably (session module loaded at boot); handle_poll is guarded like the other optional callbacks; poll_answer updates are routed to handle_poll instead of being dropped; built-in /_delete//_transit no-op on forged/malformed callback data, and /_transit verifies the handler exists before deleting the message.
  • Rate limits: sends retry once after Telegram's retry_after on 429.
  • Query format: - inside values (negative chat ids, UUIDs, dates) no longer corrupts parsing — pairs split only on - followed by key=; the library's own navigate: flow round-trips.
  • TDL: close/1 resurrected the session (permanent child of a DynamicSupervisor) or crash-looped all sessions — now terminates for real; Backend/Handler init no longer MatchError-crash-loops when the registry entry is gone; tdlib binary death is detected in non-proxy mode (:exit_status) and restarts the pair via the Riser's one_for_all (was: silent zombie); system/proxy events go directly to app_pid (were undeliverable on cold start/restart); Registry monitors session supervisors and drops dead entries; transmit/2 returns {:error, :no_backend} instead of :noproc-exiting the caller; nested vector<vector<T>> fields (inline keyboards) deserialize recursively.
  • Codegen: output directory is created (mkdir_p); generation writes atomically (malformed types.json can't destroy previous files); doc text is escaped, closing a compile-time code-execution hole; setup prompts handle :eof with a clear error.
  • Utils: fiat_chunk(-123456, 0) renders "-123 456" instead of "- 123 456"; get_message_media/2 returns nil (not :ok) for unsupported types; Object.build/1 is idempotent for already-built Objects.
  • Compatibility: removed an Elixir 1.17+ guard (is_non_struct_map/1) — the declared ~> 1.14 support is real again.
  • Lifecycle: persistent_term config is erased when the bot stops (a stopped bot no longer sends with a stale token); delayed deletions run under a supervised Task.Supervisor.

Changed

  • Sexy.TDL supervision uses :rest_for_one; per-session Risers are restart: :temporary (a persistently failing session dies alone instead of cascading into other sessions).
  • Updates of the same chat are processed in order (partitioned dispatch by chat id via Sexy.Bot.Dispatcher); different chats run concurrently. Previously all updates were dispatched as unordered concurrent tasks.
  • Documented delivery semantics: at-least-once — payment handlers should deduplicate by update_id.

0.9.15

Fixed

  • Poller no longer crashes (and crash-loops the whole supervision tree) when Telegram or a proxy returns a non-JSON body. get_updates/1 now matches status_code: 200 and decodes without Jason.decode!; a 502 HTML page returns {:error, {:http_status, 502}} instead of raising.
  • Poller error diagnostics use inspect/1 instead of Atom.to_string/1, which crashed on tuple reasons like {:tls_alert, …}.
  • Empty and errored polls keep the current offset instead of resetting to 0, so an unconfirmed update batch is no longer replayed after a transport failure (double-firing handlers, including handle_successful_payment). Errored polls also back off from 100ms to 5s instead of hot-looping.

Changed

0.9.14

Cleanup pass (ponytail audit). Breaking changes — see below.

Breaking

  • Removed Sexy.Bot.Screen. Use Sexy.Bot.build/1 (now backed by Sexy.Utils.Object.build/1).
  • Removed wallet_init/5 (Wallet.tg integration + global WALLET env). Keep it in your app — it is not Telegram Bot API.
  • send_dice/2 now takes a Telegram dice emoji ("🎲", "🎯", "🏀", "⚽", "🎳", "🎰") instead of the "dice"/"bowl"/"foot"/"bask"/"dart"/"777" codes.
  • send_chat_action/2 now takes a real Telegram action string ("typing", "upload_photo", …) instead of the "txt"/"pic"/"vid" codes.
  • get_user_photo/1 returns nil when the user has no photo, instead of a hardcoded fallback file_id. It also handles any number of photo sizes.
  • Sexy.Utils.Bot.wrap_text is now wrap_text/2 (wrapper, text), replacing the previous wrap_text/1 (decorative borders) and wrap_text/3 (HTML tag).

Fixed

  • Sexy.Utils query parsing no longer calls String.to_atom on user-supplied callback keys (atom-table exhaustion / DoS). Unknown or malformed keys are dropped.
  • mix sexy.tdl.generate_types now writes to lib/tdl/{object,method}.ex (the real paths), so regeneration overwrites the shipped files.

Internal

0.9.13

  • Multipart upload for photo, video and animation (parallel to send_document):
  • New Sexy.Utils.Object field :upload_type (:photo | :video | :animation | :document | nil) drives multipart uploads. Detection prefers upload_type over media.

  • detect_object_type/1 no longer crashes when media is not a binary (guards added; non-string media → "unknown").
  • Legacy media: "file" sentinel still works (equivalent to upload_type: :document).

0.9.12

  • Fix send_document multipart format for hackney 1.25.0. Use standard 4-tuple {"document", binary, disposition, []} instead of {binary, disposition, []}.

0.9.11

  • Add after: seconds option to notify/3 for auto-deleting notifications.

0.9.10

  • Add delayed deletion: delete_message(chat_id, mid, after: seconds). Runs asynchronously via Task — accepts integers and floats.

0.9.9

  • Fix send_document badarg when sending binary content as multipart. HTTPoison was interpreting {:file, binary, ...} as a file path instead of raw content.

0.9.8

  • Replace individual *_pid fields in Sexy.TDL.Registry with Elixir Registry-backed worker discovery (Sexy.TDL.Workers). Workers auto-unregister on death — no stale PIDs. New API: register_worker/2, get_worker/2, list_workers/1
  • Add Sexy.TDL.Workers (Elixir Registry, :unique keys) to supervision tree

0.9.7

  • Add client process pid fields to Sexy.TDL.Registry defstruct (sorter_pid, updater_pid, sender_pid, answerer_pid, reactor_pid, direct_pid)

0.9.6

  • Add Telegram Payments (Stars) support in Poller and Session:
    • Route pre_checkout_query updates to handle_pre_checkout/1 callback
    • Route successful_payment messages to handle_successful_payment/1 callback
    • Both callbacks are optional: pre_checkout auto-approves, successful_payment logs
    • successful_payment clause placed before general message to ensure correct matching

0.9.5

  • Rewrite bot-quickstart.md for beginners: add intro (single-message UI concept), BotFather step, split Session into 3 blocks (Storage, Dispatch, Screens), explain update structure, callback_query, inline_keyboard layout, answer_callback, add "How it works" flow diagram
  • Replace all Russian comments and log messages with English
  • Rename extraupdate_data in Session callback definition, docs, and examples for consistency with Sexy.Utils.Object.update_data field
  • Fix Screen module examples to use Sexy.Bot.build/send (public API) instead of Sexy.Bot.Screen.build / Sexy.Bot.Sender.deliver (internal modules)

0.9.4

  • Add comprehensive test suite: 113 tests, 0 failures (7 test files)
    • mox + bypass test dependencies for HTTP stubbing and behaviour mocking
    • Pure function tests: Utils, Utils.Bot, Utils.Object, Bot.Screen
    • Integration tests with Bypass: Bot.Api, Bot.Sender, Bot.Notification
  • Add credo dependency, pass mix credo --strict (0 issues)
  • Add 66 @spec annotations and @type definitions across 10 modules
  • Refactor get_message_type: 11-branch cond → Enum.find_value over @message_types
  • Refactor fiat_chunk: extract format_integer_part/1 + join_chunks/1, remove 100 lines of duplication
  • Refactor Sender.deliver: extract parse_mode/1, send_by_type/4, update_screen/3 (complexity 10→3, nesting 3→1)
  • Refactor Handler.recursive_match: extract recurse_if_typed/2 (nesting 3→1)
  • Fix credo issues: alias ordering, Enum.map_join, unlessif, nested module refs→aliases
  • Run mix format on all files

0.9.3

  • Add comprehensive ExDoc documentation with @moduledoc and @doc for all modules/functions
  • Add quickstart guides: bot-quickstart.md, tdl-quickstart.md
  • Add hex.pm packaging config (description, package, docs)
  • Filter auto-generated TDL.Object/Method submodules from docs
  • Expand README with Object struct, send options, and notify options documentation
  • Add MIT LICENSE

0.9.2

  • Flatten lib/ structure: remove redundant lib/sexy/ nesting

0.9.1

0.9.0

Breaking: Sexy split into Sexy.Bot + Sexy.TDL.

0.8.2

  • Add Telegram Stars payment methods (send_invoice, answer_pre_checkout, refund_star_payment)
  • Add wallet_init for Wallet.tg integration

0.8.1

  • Built-in /_transit route for navigation between screens

0.8.0

Breaking: Sexy is now a Supervisor, not an Application.

  • Start with {Sexy, token: "...", session: MySession} in supervision tree
  • Sexy.Session behaviour — single integration point (persistence + dispatch)
  • Removed Sexy.Application, Sexy.Visor, config-based dispatch
  • Config replaced with persistent_term (no more :sexycon)
  • Added build/1, send/1-2, notify/2-3 facade functions
  • Hardcoded dismiss_text default to "OK"
  • Removed wrap_text from Sender

0.7.4

  • Add Sexy.Notification — overlay/replace notification pattern
  • Dismiss button with built-in /_delete route in Poller
  • Configurable dismiss_text via Application.get_env(:sexy, :dismiss_text)

0.7.3

  • Remove 26 legacy short-name delegates (pm, dm, um, ui, etc.)
  • Remove unused api_url/1

0.7.2

  • Add Sexy.Session behaviour (get_message_id/1, on_message_sent/4)
  • Add Sexy.Sender — delivers Object, manages mid lifecycle via Session callbacks
  • Add Sexy.Screen — converts app maps to Object structs
  • Sexy.Utils.Object: add chat_id, rename update_userupdate_data

0.7.1

  • Create Sexy.Api with readable method names (send_message, delete_message, etc.)
  • Consolidate HTTP into do_request/3 and do_multipart/3
  • Rewrite Sexy as facade with defdelegate to Sexy.Api
  • Legacy short names kept as backward-compat delegates

0.7.0

  • Initial extraction from OrderMachine into standalone library
  • Modules: Sexy (API client), Sexy.Poller, Sexy.Visor, Sexy.Utils, Sexy.Utils.Bot, Sexy.Utils.Object
  • Dependencies: HTTPoison, Jason, Base62