0.10.0
Reliability release: 37 audited defects fixed. Breaking changes — see UPGRADING.md for the per-call migration guide.
Breaking
Sexy.Bot.send/2(andSender.deliver/2) with a list returns per-item responses instead of:ok.Sexy.Bot.start_link/1raisesArgumentErroron a nil/empty token or an unloadable session module.Sexy.Bot.notify/3returns"ok" => false(with"result"preserved) when buttons could not be attached.Sexy.TDL.open/3returns{:error, {:already_started, pid}}for duplicate session names and{:error, reason}when the port can't open (was{:ok, pid}+ silence).mix sexy.tdl.setupno longer generates types (they ship with the package);mix sexy.tdl.generate_typesrefuses to run outside the sexy repo unless--force.
Fixed
- Poller: a malformed update (e.g.
callback_querywithoutdata) 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 bysend_afterand 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 raisingJason.DecodeError; transport-error descriptions no longer crash on tuple reasons. - Payments: optional Session callbacks are resolved reliably (session module loaded at boot);
handle_pollis guarded like the other optional callbacks;poll_answerupdates are routed tohandle_pollinstead of being dropped; built-in/_delete//_transitno-op on forged/malformed callback data, and/_transitverifies the handler exists before deleting the message. - Rate limits: sends retry once after Telegram's
retry_afteron 429. - Query format:
-inside values (negative chat ids, UUIDs, dates) no longer corrupts parsing — pairs split only on-followed bykey=; the library's ownnavigate:flow round-trips. - TDL:
close/1resurrected the session (permanent child of a DynamicSupervisor) or crash-looped all sessions — now terminates for real; Backend/Handler init no longerMatchError-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'sone_for_all(was: silent zombie); system/proxy events go directly toapp_pid(were undeliverable on cold start/restart); Registry monitors session supervisors and drops dead entries;transmit/2returns{:error, :no_backend}instead of:noproc-exiting the caller; nestedvector<vector<T>>fields (inline keyboards) deserialize recursively. - Codegen: output directory is created (
mkdir_p); generation writes atomically (malformedtypes.jsoncan't destroy previous files); doc text is escaped, closing a compile-time code-execution hole; setup prompts handle:eofwith a clear error. - Utils:
fiat_chunk(-123456, 0)renders"-123 456"instead of"- 123 456";get_message_media/2returnsnil(not:ok) for unsupported types;Object.build/1is idempotent for already-built Objects. - Compatibility: removed an Elixir 1.17+ guard (
is_non_struct_map/1) — the declared~> 1.14support 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.TDLsupervision uses:rest_for_one; per-session Risers arerestart: :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/1now matchesstatus_code: 200and decodes withoutJason.decode!; a 502 HTML page returns{:error, {:http_status, 502}}instead of raising. - Poller error diagnostics use
inspect/1instead ofAtom.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, includinghandle_successful_payment). Errored polls also back off from 100ms to 5s instead of hot-looping.
Changed
Sexy.Bot.get_updates/1(andSexy.Bot.Api.get_updates/1) error return widened: alongside{:error, atom}it can now return{:error, {:http_status, integer}}or{:error, :invalid_json}. Bare:error(noresultkey) was already possible.
0.9.14
Cleanup pass (ponytail audit). Breaking changes — see below.
Breaking
- Removed
Sexy.Bot.Screen. UseSexy.Bot.build/1(now backed bySexy.Utils.Object.build/1). - Removed
wallet_init/5(Wallet.tg integration + globalWALLETenv). Keep it in your app — it is not Telegram Bot API. send_dice/2now takes a Telegram dice emoji ("🎲","🎯","🏀","⚽","🎳","🎰") instead of the"dice"/"bowl"/"foot"/"bask"/"dart"/"777"codes.send_chat_action/2now takes a real Telegram action string ("typing","upload_photo", …) instead of the"txt"/"pic"/"vid"codes.get_user_photo/1returnsnilwhen the user has no photo, instead of a hardcoded fallbackfile_id. It also handles any number of photo sizes.Sexy.Utils.Bot.wrap_textis nowwrap_text/2(wrapper, text), replacing the previouswrap_text/1(decorative borders) andwrap_text/3(HTML tag).
Fixed
Sexy.Utilsquery parsing no longer callsString.to_atomon user-supplied callback keys (atom-table exhaustion / DoS). Unknown or malformed keys are dropped.mix sexy.tdl.generate_typesnow writes tolib/tdl/{object,method}.ex(the real paths), so regeneration overwrites the shipped files.
Internal
- Deduplicated HTTP response decoding in
Sexy.Bot.Api(decode_response/1). get_photo_id/1simplified toList.last/1; sharedtitlecase_once/1moved toSexy.Utils.
0.9.13
- Multipart upload for photo, video and animation (parallel to
send_document):- New:
Sexy.Bot.Api.send_photo/5,send_video/5,send_animation/5 - Top-level delegates:
Sexy.Bot.send_photo/5,send_video/5,send_animation/5
- New:
New
Sexy.Utils.Objectfield:upload_type(:photo | :video | :animation | :document | nil) drives multipart uploads. Detection prefersupload_typeovermedia.detect_object_type/1no longer crashes whenmediais not a binary (guards added; non-string media →"unknown").- Legacy
media: "file"sentinel still works (equivalent toupload_type: :document).
0.9.12
- Fix
send_documentmultipart format for hackney 1.25.0. Use standard 4-tuple{"document", binary, disposition, []}instead of{binary, disposition, []}.
0.9.11
- Add
after: secondsoption tonotify/3for auto-deleting notifications.
0.9.10
- Add delayed deletion:
delete_message(chat_id, mid, after: seconds). Runs asynchronously viaTask— accepts integers and floats.
0.9.9
- Fix
send_documentbadarg when sending binary content as multipart. HTTPoison was interpreting{:file, binary, ...}as a file path instead of raw content.
0.9.8
- Replace individual
*_pidfields inSexy.TDL.Registrywith ElixirRegistry-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,:uniquekeys) to supervision tree
0.9.7
- Add client process pid fields to
Sexy.TDL.Registrydefstruct (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_queryupdates tohandle_pre_checkout/1callback - Route
successful_paymentmessages tohandle_successful_payment/1callback - Both callbacks are optional: pre_checkout auto-approves, successful_payment logs
successful_paymentclause placed before generalmessageto ensure correct matching
- Route
0.9.5
- Rewrite
bot-quickstart.mdfor 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
extra→update_datain Session callback definition, docs, and examples for consistency withSexy.Utils.Object.update_datafield - Fix Screen module examples to use
Sexy.Bot.build/send(public API) instead ofSexy.Bot.Screen.build/Sexy.Bot.Sender.deliver(internal modules)
0.9.4
- Add comprehensive test suite: 113 tests, 0 failures (7 test files)
mox+bypasstest 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
credodependency, passmix credo --strict(0 issues) - Add 66
@specannotations and@typedefinitions across 10 modules - Refactor
get_message_type: 11-branch cond →Enum.find_valueover@message_types - Refactor
fiat_chunk: extractformat_integer_part/1+join_chunks/1, remove 100 lines of duplication - Refactor
Sender.deliver: extractparse_mode/1,send_by_type/4,update_screen/3(complexity 10→3, nesting 3→1) - Refactor
Handler.recursive_match: extractrecurse_if_typed/2(nesting 3→1) - Fix credo issues: alias ordering,
Enum.map_join,unless→if, nested module refs→aliases - Run
mix formaton all files
0.9.3
- Add comprehensive ExDoc documentation with
@moduledocand@docfor 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 redundantlib/sexy/nesting
0.9.1
- Move all Bot modules under
Sexy.Botnamespace:Sexy.Api→Sexy.Bot.ApiSexy.Sender→Sexy.Bot.SenderSexy.Notification→Sexy.Bot.NotificationSexy.Poller→Sexy.Bot.PollerSexy.Screen→Sexy.Bot.ScreenSexy.Session→Sexy.Bot.Session
0.9.0
Breaking: Sexy split into Sexy.Bot + Sexy.TDL.
Sexymodule is now a namespace only — useSexy.Botas supervisor entry point- All Bot API functions moved:
Sexy.*→Sexy.Bot.* - New
Sexy.TDL— TDLib integration for userbot sessionsSexy.TDL.open/3,close/1,transmit/2— session managementSexy.TDL.Backend— port to tdlib_json_cli binarySexy.TDL.Handler— JSON deserialization + event routingSexy.TDL.Registry— ETS-based session storageSexy.TDL.Riser— per-account supervisor- 2558 auto-generated Method/Object structs from TDLib API
- New mix tasks:
mix sexy.tdl.setup,mix sexy.tdl.generate_types - Migration guide: see MIGRATION.md
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.Sessionbehaviour — 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-3facade functions - Hardcoded dismiss_text default to "OK"
- Removed
wrap_textfrom Sender
0.7.4
- Add
Sexy.Notification— overlay/replace notification pattern - Dismiss button with built-in
/_deleteroute in Poller - Configurable
dismiss_textviaApplication.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.Sessionbehaviour (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: addchat_id, renameupdate_user→update_data
0.7.1
- Create
Sexy.Apiwith readable method names (send_message, delete_message, etc.) - Consolidate HTTP into
do_request/3anddo_multipart/3 - Rewrite
Sexyas facade withdefdelegatetoSexy.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