{"id":13507539,"url":"https://github.com/rockneurotiko/ex_gram","last_synced_at":"2026-03-13T01:10:46.787Z","repository":{"id":19602599,"uuid":"78295322","full_name":"rockneurotiko/ex_gram","owner":"rockneurotiko","description":"Telegram Bot API low level API and framework","archived":false,"fork":false,"pushed_at":"2025-04-22T15:26:25.000Z","size":637,"stargazers_count":192,"open_issues_count":21,"forks_count":28,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-13T20:27:49.939Z","etag":null,"topics":["api","bot","elixir","elixir-lang","hacktoberfest","telegram","telegram-bot-api"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rockneurotiko.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-01-07T18:12:25.000Z","updated_at":"2025-04-26T14:37:08.000Z","dependencies_parsed_at":"2024-03-14T22:44:03.192Z","dependency_job_id":"f59c186d-5947-4b24-979e-a3f49a8e517d","html_url":"https://github.com/rockneurotiko/ex_gram","commit_stats":{"total_commits":207,"total_committers":16,"mean_commits":12.9375,"dds":0.5700483091787439,"last_synced_commit":"25f28599672aaca465bd48b8687428ee97ce247f"},"previous_names":[],"tags_count":66,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockneurotiko%2Fex_gram","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockneurotiko%2Fex_gram/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockneurotiko%2Fex_gram/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockneurotiko%2Fex_gram/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rockneurotiko","download_url":"https://codeload.github.com/rockneurotiko/ex_gram/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254337613,"owners_count":22054253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","bot","elixir","elixir-lang","hacktoberfest","telegram","telegram-bot-api"],"created_at":"2024-08-01T02:00:35.901Z","updated_at":"2026-03-13T01:10:46.697Z","avatar_url":"https://github.com/rockneurotiko.png","language":"Elixir","funding_links":[],"categories":["Chatting","Telegram Libraries"],"sub_categories":["Elixir"],"readme":"# ExGram\n\n[![Hex.pm](https://img.shields.io/hexpm/v/ex_gram.svg)](http://hex.pm/packages/ex_gram)\n[![Hex.pm](https://img.shields.io/hexpm/dt/ex_gram.svg)](https://hex.pm/packages/ex_gram)\n[![Hex.pm](https://img.shields.io/hexpm/dw/ex_gram.svg)](https://hex.pm/packages/ex_gram)\n[![Build Status](https://travis-ci.com/rockneurotiko/ex_gram.svg?branch=master)](https://travis-ci.com/rockneurotiko/ex_gram)\n\nExGram is a library to build Telegram Bots, you can use the low-level methods and models, or use the really opinionated framework included.\n\n## Installation\n\nAdd `ex_gram` as dependency in `mix.exs`\n\n``` elixir\ndef deps do\n    [\n      {:ex_gram, \"~\u003e 0.54\"},\n      {:tesla, \"~\u003e 1.2\"},\n      {:hackney, \"~\u003e 1.12\"},\n      {:jason, \"\u003e= 1.0.0\"}\n    ]\nend\n```\n\nSee the next sections to select a different HTTP adapter or JSON engine.\n\n### HTTP Adapter\n\nYou should add Tesla or custom HTTP adapter, by default it will try to use the Tesla adapter, these are the defaults:\n\nOn deps:\n``` elixir\n{:tesla, \"~\u003e 1.2\"},\n{:hackney, \"~\u003e 1.12\"}\n```\n\n- If you want to use Gun:\n\nOn deps:\n``` elixir\n{:tesla, \"~\u003e 1.2\"},\n{:gun, \"~\u003e 1.3\"}\n```\n\nOn config:\n``` elixir\nconfig :tesla, adapter: Tesla.Adapter.Gun\n```\n\n- If you prefer your custom adapter instead of Tesla:\n\nIt must implement the behaviour `ExGram.Adapter`\n\nOn config:\n\n``` elixir\nconfig :ex_gram, adapter: YourCustomAdapter\n```\n\n### JSON Engine\n\nBy default ExGram will use `Jason` engine, but you can change it to your preferred JSON engine, the module just has to expose `encode/2`, `encode!/2`, `decode/2`, `decode!/2`.\n\nYou can change the engine in the configuration:\n\n``` elixir\nconfig :ex_gram, json_engine: Poison\n```\n\n## Configuration\n\nThere are some optional configurations that you can add to your `config.exs`:\n\n\n### Token\n\n\n``` elixir\nconfig :ex_gram, token: \"TOKEN\"\n```\n\nThis configuration will be used by default, but you can specify on every call a token or a bot to use.\n\nIf you use the framework, you will need to add `ExGram` and your bot (let's say it's `MyBot`) to your application:\n\n``` elixir\nchildren = [\n  ExGram, # This will setup the Registry.ExGram\n  {MyBot, [method: :polling, token: \"TOKEN\"]}\n]\n```\n\n### Polling mode\n\nThe easiest way to get your bot runnig is using the Polling mode, it will use the method `getUpdates` on the telegram API to receive the new updates. You can read more about it here: https://core.telegram.org/bots/api#getting-updates\n\nSetting this mode is as easy as defining the mode and the token in your supervisor:\n\n``` elixir\nchildren = [\n  # ...\n  {MyBot, [method: :polling, token: \"TOKEN\"]}\n]\n```\n\nAdditionally, you can configure the `getUpdates` call on the children options or on the application configuration.\n\n- In children options\n\n``` elixir\nchildren = [\n  # ...\n  {MyBot, [method: {:polling, allowed_updates: [\"message\", \"edited_message\"]}, token: \"TOKEN\"]}\n]\n```\n\n- In application configuration\n\n``` elixir\nconfig :ex_gram, :polling, allowed_updates: [\"message\", \"edited_message\"]\n```\n\nWebhooks might cause some issues if you are doing polling but if you have never used webhooks you can configure to not delete it.\n\n```elixir\n# This will not delete the webhook because it is never created.\n# by default :delete_webhook is true\nconfig :ex_gram, :polling, allowed_updates: [\"message\", \"edited_message\"], delete_webhook: false\n```\n\nThis configuration takes priority over the ones on the configuration files, but you can combine them, for example having a default `allowed_updates` in the application configuration and in some bots where you need other updates overide it on the children options.\n\n\n### Webhook mode\n\nIf you prefer to use webhook to have more performance receiving updates, you can use the provided Webhook mode.\n\nThe provided Webhook adapter uses `Plug`, you will need to have that dependency in your application, and add it to your router, with basic Plug Router it would look something like this:\n\n``` elixir\ndefmodule AppRouter do\n  use Plug.Router\n\n  plug ExGram.Plug\nend\n```\n\nAt the moment the webhook URL will be `/telegram/\u003cbot_token_hash\u003e`.\n\nThen, in your bots you have to specify the webhook updater when you start it on your supervisor tree:\n\n``` elixir\nchildren = [\n  # ...\n  {MyBot, [method: :webhook, token: \"TOKEN\"]}\n]\n```\n\nIn webhook mode, you can configure the following parameters:\n\n``` elixir\nconfig :ex_gram, :webhook,\n  allowed_updates: [\"message\", \"poll\"],       # array of strings\n  certificate: \"priv/cert/selfsigned.pem\",    # string (file path)\n  drop_pending_updates: false,                # boolean\n  ip_address: \"1.1.1.1\",                      # string\n  max_connections: 50,                        # integer\n  secret_token: \"some_super_secret_key\",      # string\n  url: \"http://bot.example.com:4000\"          # string (domain name with scheme and maybe port)\n```\n\nYou can also configure this options when starting inside the children options, you can configure it this way to ensure fine-grained setup per bot.\n\nExample:\n\n``` elixir\nwebhook_options = [allowed_updates: [\"message\", \"poll\"], certificate: \"priv/...\", ...] # All options described before\nchildren = [\n  # We use a tuple instead of the atom\n  {MyBot, [method: {:webhook, webhook_options}, token: \"TOKEN\"]}\n]\n```\n\nThis configuration takes priority over the ones on the configuration files, but you can combine them, for example configuring the `certificate`, `ip_address` and `url` in the config file and the `allowed_updates` and `drop_pending_updates` in the children options.\n\nFor more information on each parameter, refer to this documentation: https://core.telegram.org/bots/api#setwebhook\n\n### Test environment\n\nTelegram has a Test Environment that you can use to test your bots, you can learn how to setup your bots there in this documentation: https://core.telegram.org/bots/webapps#using-bots-in-the-test-environment\n\nIn order to use the Test Environment you need to configure the bot like this:\n\n``` elixir\nconfig :ex_gram, test_environment: true\n```\n\n### Configure Tesla middlewares\n\nIf you are using the `Tesla` adapter, you can add [Tesla\nmiddlewares](https://github.com/teamon/tesla#middleware) to `ExGram`\nvia config file. Add to your config:\n```elixir\nconfig :ex_gram, ExGram.Adapter.Tesla,\n  middlewares: [\n    {Tesla.Middleware.BaseUrl, \"https://example.com/foo\"}\n  ]\n```\n\nThe `middlewares` list will be loaded in the `ExGram.Adapter.Tesla` module.\n\nIn case you want to use a middleware that requires a function or any\ninvalid element for a configuration file, you can define a function in\nany module that returns the Tesla configuration. Then put the `{m, f,\na}` in the configuration file, for example:\n```elixir\n# lib/tesla_middlewares.ex\n\ndefmodule TeslaMiddlewares do\n  def retry() do\n    {Tesla.Middleware.Retry,\n     delay: 500,\n     max_retries: 10,\n     max_delay: 4_000,\n     should_retry: fn\n       {:ok, %{status: status}} when status in [400, 500] -\u003e true\n       {:ok, _} -\u003e false\n       {:error, _} -\u003e true\n     end}\n  end\nend\n```\n\nAnd in the config file:\n```elixir\n# config/config.exs\n\nconfig :ex_gram, ExGram.Adapter.Tesla,\n  middlewares: [\n    {TeslaMiddlewares, :retry, []}\n  ]\n```\n\nTake into account that the defined function has to return a two-tuple\nas the Tesla config requires.\n\n## Framework Usage\n\nThis section will show how to use the opinionated framework `ex_gram` for Telegram bots!\n\n### Creating a bot!\n\nCreating a bot is pretty simple, you can use the `mix bot.new` task to setup your bot. For example:\n\n``` shell\n$ mix new my_bot --sup\n$ cd my_bot\n```\n\nAdd and setup `ExGram` and it's adapters in your project as shown in the [Installation](#installation) section. After that, get the project deps and run the bot new task:\n\n``` shell\n$ mix deps.get\n$ mix bot.new\n```\n\nYou will get a message like this:\n\n``` text\nYou should also add ExGram and MyBot.Bot as children of the application Supervisor,\nhere is an example using polling:\n\nchildren = [\n  ExGram,\n  {MyBot.Bot, [method: :polling, token: token]}\n]\n```\n\nThis is basically telling you to configure the project as shown in the [Configuration](#configuration) section. Get your token and put `ExGram` and `MyBot.Bot` under the `Application`.\n\nNow you are ready to run the bot with `mix run --no-halt`, go to Telegram and send your bot the command `/start`.\n\n### How to handle messages\n\nIf you followed the [Creating a bot!](#creating-a-bot) section you should see a `handle/2` function in your `MyBot.Bot` module that looks like this:\n\n``` elixir\ndef handle({:command, \"start\", _msg}, context) do\n  answer(context, \"Hi!\")\nend\n```\n\nThe `handle/2` function receives two arguments:\n  - The first argument is a tuple that changes depending on the update. In this case we are expecting a command called `start` in Telegram, this means a `/start` message. This type of commands can be sent next to a message, for example `/start Well hello`, in this cases the `Well hello` text will arrive to the third element of the tuple named `_msg` (because we are ignoring it right now). In case no text is given an empty string will arrive in the third element.\n\n  - The second argument is a map called Context (`%ExGram.Cnt{}`) with information about the update that just arrived, with information like the [message object](https://core.telegram.org/bots/api#message) and internal data that `ExGram` will use to answer the message. You can also save your own information from your own middlewares in the `:extra` key using the `add_extra` method.\n\nThis are the type of tuples that `handle/2` can receive as first parameter:\n  - `{:command, key, message}` → This tuple will match when a command is received\n  - `{:text, text, message}` → This tuple will match when plain text is sent to the bot (check [privacy mode](https://core.telegram.org/bots#privacy-mode))\n  - `{:regex, key, message}` → This tuple will match if a regex is defined at the beginning of the module\n  - `{:location, location}` → This tuple will match when a location message is received\n  - `{:callback_query, callback_query}` → This tuple will match when a [Callback Query](https://core.telegram.org/bots/api#callbackquery) is received\n  - `{:inline_query, inline_query}` → This tuple will match when an [Inline Query](https://core.telegram.org/bots/api#inlinequery) is received\n  - `{:edited_message, edited_message}` → This tuple will match when a message is edited\n  - `{:message, message}` → This will match any message that does not fit with the ones described above\n  - `{:update, update}` → This tuple will match as a default handle\n\n### Execute code on initialization\n\nThe bots have an optional callback that will be executed *before* starting to consume messages. This method can be used to initialize things before starting the bot, for example setting the bot's description or name.\n\nThe callback is `init/1`, the parameter is a keyword list with two values, `:bot` which is the bot's name, and `:token` with the token used when starting the bot. Either of this can be used when calling `ExGram` methods.\n\nExample of usage:\n\n``` elixir\ndefmodule MyBot.Bot do\n  @bot :my_bot\n\n  use ExGram.Bot, name: @bot\n\n  def init(opts) do\n    ExGram.set_my_description!(description: \"This is my description\", bot: opts[:bot]) # with :bot\n    ExGram.set_my_name!(name: \"My Bot\", token: opts[:token]) # with :token\n    :ok\n  end\n\n  # ...\nend\n```\n\n### Sending files\n\n`ExGram` lets you send files by id (this means using files already uploaded to Telegram servers), providing a local path, or with the content directly. Some examples of this methods for sending files:\n``` elixir\nExGram.send_document(chat_id, document_id)                                     # By document ID\n\nExGram.send_document(chat_id, {:file, \"path/to/file\"})                         # By local path\n\nExGram.send_document(chat_id, {:file_content, \"FILE CONTENT\", \"filename.txt\"}) # By content\n```\n\nThis three ways of sending files works when the API has a file field, for example `send_photo`, `send_audio`, `send_video`, ...\n\n## Library Usage\n\nSometimes you just want to be able to send messages to some channel, or you don't like the way the framework works and want to be your own manager of the messages flows. For that cases, the low level API allows you to use the `ex_gram` library as raw as possible.\n\nYou can configure `ex_gram` in `config.exs` as explained in the Configuration section (you don't need to add anything to the application if you don't want to use the framework) and just use the low level API, for example:\n\n``` elixir\nExGram.send_message(\"@my_channel\", \"Sending messages!!!\")\n```\n\nAlternatively, you can not configure `ex_gram` at all (or use this to use different bots, having one configured or not), and use the extra parameter `token`:\n\n``` elixir\nExGram.send_message(\"@my_channel\", \"Sending messages!!!\", token: \"BOT_TOKEN\")\n```\n\nIf you want to know how the low level API is designed and works, you can read the next section.\n\n\n## Low level API\n\nAll the models and methods are equal one to one with the models and methods defined on the [Telegram Bot API Documentation](https://core.telegram.org/bots/api)!\n\n### Models\n\nAll the models are inside of the `ExGram.Model` module. You can see all the models in `lib/ex_gram.ex` file, for example `User`:\n\n``` elixir\nmodel(User, [\n  {:id, :integer},\n  {:is_bot, :boolean},\n  {:first_name, :string},\n  {:last_name, :string},\n  {:username, :string},\n  {:language_code, :string}\n])\n```\n\nAlso, all the models have the type `t` defined, so you can use it on your typespecs or see their types inside of an IEx console:\n\n``` elixir\n\u003e\u003e\u003e t ExGram.Model.User\n@type t() :: %ExGram.Model.User{\n  first_name: String.t(),\n  id: integer(),\n  is_bot: boolean(),\n  language_code: String.t(),\n  last_name: String.t(),\n  username: String.t()\n}\n```\n\n### Methods\n\nAll the methods are inside of the `ExGram` module, they are like the documentation ones but in snake_case instead of camelCase.\n\nIf a method has mandatory arguments they will be the arguments (in order that are defined on the documentation) to the method, all the optional values will go in the last argument as keyword list.\n\nAlso, the parameters must be of the types defined on the documentation (if multiple types, it must be one of them), and the method will return the model assigned of the one in the documentation. If you want to see the parameters and types that a method gets and returns, you can use the `h` method in an `IEx` instance:\n\n``` elixir\n\u003e\u003e\u003e h ExGram.send_message\n\ndef send_message(chat_id, text, ops \\\\ [])\n\n@spec send_message(\n  chat_id :: integer() | String.t(),\n  text :: String.t(),\n  ops :: [\n    parse_mode: String.t(),\n    disable_web_page_preview: boolean(),\n    disable_notification: boolean(),\n    reply_to_message_id: integer(),\n    reply_markup:\n      ExGram.Model.InlineKeyboardMarkup.t()\n      | ExGram.Model.ReplyKeyboardMarkup.t()\n      | ExGram.Model.ReplyKeyboardRemove.t()\n      | ExGram.Model.ForceReply.t()\n    ]\n) :: {:ok, ExGram.Model.Message.t()} | {:error, ExGram.Error.t()}\n```\n\nAll the methods have their unsafe brother with the name banged(!) (`get_me!` for the `get_me` method) that instead of returning `{:ok, model} | {:error, ExGram.Error}` will return `model` and raise if there is some error.\n\nFor example, the method \"getUpdates\" from the documentation will be `get_updates`, and this one takes 4 optional parameters. We'll use the parameters `offset` and `limit`:\n\n``` elixir\nExGram.get_updates(offset: 123, limit: 100)\n```\n\nAnother example, the method \"sendMessage\" is `send_message`, this one has two mandatory parameters, `chat_id` (either an integer or a string), `text` (a string), and 5 optional parameters:\n\n``` elixir\nExGram.send_message(\"@rockneurotiko\", \"Hey bro! Checkout the ExGram library!\", disable_notification: true)\n```\n\n### Extra options\n\nAll the methods have three extra options:\n\n- `debug`: When `true` it will print the HTTP request response.\n- `token`: It will use this token for the request.\n- `bot`: It will search on `Registry.ExGram` the `bot` name to extract the token. This registry is set up by `ExGram`, and all the bots made by the framework will register on it.\n\nNote: Only one of `token` and `bot` must be used.\n\n### How it's made?\n\nThere is a Python script called `extractor.py`, it uses the [`telegram_api_json`](https://github.com/rockneurotiko/telegram_api_json) project that scrapes the Telegram Bot API documentation and provides a JSON with all the information, check the project description if you want to create your own projects that uses a standardized file to auto-generate the API.\n\nThis script uses the JSON description and prints to the stdout the lines needed to create all the methods and models, these auto-generated lines use two macros defined on `lib/ex_gram/macros.ex`: `method` and `model`.\n\n#### Custom types defined\n\n- `:string` -\u003e `String.t()`\n- `:int` or `:integer` -\u003e `integer`\n- `:bool` or `:boolean` -\u003e `boolean`\n- `:file` -\u003e `{:file, String.t()}`\n- `{:array, t}` -\u003e `[t]`\n- Any `ExGram.Model`\n\n\n#### Model macro\n\nParameters:\n1. Name of the model\n2. Properties of the model, it's a list of tuples, where the first parameter is the name of the property and the second one is the type.\n\nThis macro is the simple one, just create a module with the first name passed and use the params to create the struct and typespecs.\n\n#### Method macro\n\nParameters:\n1. Verb of the method (`:get` or `:post`)\n2. Name of the method as string, it will be underscored.\n3. The parameters of the method, this is a list of tuples, the tuples contains:\n- Name of the parameters\n- Type(s) of the parameter, it is a list of types, if there are more than one type on the list, it is expected to have one of them.\n- An optional third parameter (always `:optional`) to set that parameter as optional\n4. The type to be returned, it can be a model.\n\nThe macro will create two methods, one that will return the tuple `ok|error`, and a banged(!) version that will raise if there is some error.\n\nThese methods do some stuff, like retrieving the token, checking the parameters types, setting up the body of some methods/verbs (specially the ones with files), calling the method and parsing the result.\n\n## Creating your own updates worker\n\nThe ExGram framework uses updates worker to \"receive\" the updates and send them to the dispatcher, this is the first parameter that you provide to your bot, the ones currently are `:polling` that goes to the module `ExGram.Updates.Polling` for polling updates, `:webhook` that goes to the module `ExGram.Updates.Webhook` for webhook updates and `:noup` that uses `ExGram.Updates.NoUp` that do nothing (great for some offline testing). Sadly, the test worker are on the way.\n\nBut you can implement your own worker to retrieve the updates as you want!\n\nThe only specs are that `start_link` will receive `{:bot, \u003cpid\u003e, :token, \u003ctoken\u003e}`, the PID is where you should send the updates, and the token that your worker will be able to use to retrieve the updates.\n\nWhenever you have an update `ExGram.Model.Update`, send it to the bot's PID like: `{:update, \u003cupdate\u003e}` with `GenServer.call`.\n\nYou can see the code of `ExGram.Updates.Polling`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frockneurotiko%2Fex_gram","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frockneurotiko%2Fex_gram","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frockneurotiko%2Fex_gram/lists"}