Code Monkey home page Code Monkey logo

ex_gram's Introduction

ExGram

Hex.pm Hex.pm Hex.pm Build Status

ExGram is a library to build Telegram Bots, you can use the low-level methods and models, or use the really opinionated framework included.

Installation

Add ex_gram as dependency in mix.exs

def deps do
    [
      {:ex_gram, "~> 0.52"},
      {:tesla, "~> 1.2"},
      {:hackney, "~> 1.12"},
      {:jason, ">= 1.0.0"}
    ]
end

See the next sections to select a different HTTP adapter or JSON engine.

HTTP Adapter

You should add Tesla or custom HTTP adapter, by default it will try to use the Tesla adapter, these are the defaults:

On deps:

{:tesla, "~> 1.2"},
{:hackney, "~> 1.12"}
  • If you want to use Gun:

On deps:

{:tesla, "~> 1.2"},
{:gun, "~> 1.3"}

On config:

config :tesla, adapter: Tesla.Adapter.Gun
  • If you prefer your custom adapter instead of Tesla:

It must implement the behaviour ExGram.Adapter

On config:

config :ex_gram, adapter: YourCustomAdapter

JSON Engine

By 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.

You can change the engine in the configuration:

config :ex_gram, json_engine: Poison

Configuration

There are some optional configurations that you can add to your config.exs:

Token

config :ex_gram, token: "TOKEN"

This configuration will be used by default, but you can specify on every call a token or a bot to use.

If you use the framework, you will need to add ExGram and your bot (let's say it's MyBot) to your application:

children = [
  ExGram, # This will setup the Registry.ExGram
  {MyBot, [method: :polling, token: "TOKEN"]}
]

Polling mode

The 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

Setting this mode is as easy as defining the mode and the token in your supervisor:

children = [
  # ...
  {MyBot, [method: :polling, token: "TOKEN"]}
]

Additionally, you can configure the getUpdates call on the children options or on the application configuration.

  • In children options
children = [
  # ...
  {MyBot, [method: {:polling, allowed_updates: ["message", "edited_message"]}, token: "TOKEN"]}
]
  • In application configuration
config :ex_gram, :polling, allowed_updates: ["message", "edited_message"]

This 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.

Webhook mode

If you prefer to use webhook to have more performance receiving updates, you can use the provided Webhook mode.

The 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:

defmodule AppRouter do
  use Plug.Router

  plug ExGram.Plug
end

At the moment the webhook URL will be /telegram/<bot_token_hash>.

Then, in your bots you have to specify the webhook updater when you start it on your supervisor tree:

children = [
  # ...
  {MyBot, [method: :webhook, token: "TOKEN"]}
]

In webhook mode, you can configure the following parameters:

config :ex_gram, :webhook,
  allowed_updates: ["message", "poll"],       # array of strings
  certificate: "priv/cert/selfsigned.pem",    # string (file path)
  drop_pending_updates: false,                # boolean
  ip_address: "1.1.1.1",                      # string
  max_connections: 50,                        # integer
  secret_token: "some_super_secret_key",      # string
  url: "http://bot.example.com:4000"          # string (domain name with scheme and maybe port)

You can also configure this options when starting inside the children options, you can configure it this way to ensure fine-grained setup per bot.

Example:

webhook_options = [allowed_updates: ["message", "poll"], certificate: "priv/...", ...] # All options described before
children = [
  # We use a tuple instead of the atom
  {MyBot, [method: {:webhook, webhook_options}, token: "TOKEN"]}
]

This 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.

For more information on each parameter, refer to this documentation: https://core.telegram.org/bots/api#setwebhook

Test environment

Telegram 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

In order to use the Test Environment you need to configure the bot like this:

config :ex_gram, test_environment: true

Configure Tesla middlewares

If you are using the Tesla adapter, you can add Tesla middlewares to ExGram via config file. Add to your config:

config :ex_gram, ExGram.Adapter.Tesla,
  middlewares: [
    {Tesla.Middleware.BaseUrl, "https://example.com/foo"}
  ]

The middlewares list will be loaded in the ExGram.Adapter.Tesla module.

In case you want to use a middleware that requires a function or any invalid element for a configuration file, you can define a function in any module that returns the Tesla configuration. Then put the {m, f, a} in the configuration file, for example:

# lib/tesla_middlewares.ex

defmodule TeslaMiddlewares do
  def retry() do
    {Tesla.Middleware.Retry,
     delay: 500,
     max_retries: 10,
     max_delay: 4_000,
     should_retry: fn
       {:ok, %{status: status}} when status in [400, 500] -> true
       {:ok, _} -> false
       {:error, _} -> true
     end}
  end
end

And in the config file:

# config/config.exs

config :ex_gram, ExGram.Adapter.Tesla,
  middlewares: [
    {TeslaMiddlewares, :retry, []}
  ]

Take into account that the defined function has to return a two-tuple as the Tesla config requires.

Framework Usage

This section will show how to use the opinionated framework ex_gram for Telegram bots!

Creating a bot!

Creating a bot is pretty simple, you can use the mix bot.new task to setup your bot. For example:

$ mix new my_bot --sup
$ cd my_bot

Add and setup ExGram and it's adapters in your project as shown in the Installation section. After that, get the project deps and run the bot new task:

$ mix deps.get
$ mix bot.new

You will get a message like this:

You should also add ExGram and MyBot.Bot as children of the application Supervisor,
here is an example using polling:

children = [
  ExGram,
  {MyBot.Bot, [method: :polling, token: token]}
]

This is basically telling you to configure the project as shown in the Configuration section. Get your token and put ExGram and MyBot.Bot under the Application.

Now you are ready to run the bot with mix run --no-halt, go to Telegram and send your bot the command /start.

How to handle messages

If you followed the Creating a bot! section you should see a handle/2 function in your MyBot.Bot module that looks like this:

def handle({:command, "start", _msg}, context) do
  answer(context, "Hi!")
end

The handle/2 function receives two arguments:

  • 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.

  • The second argument is a map called Context (%ExGram.Cnt{}) with information about the update that just arrived, with information like the message object 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.

This are the type of tuples that handle/2 can receive as first parameter:

  • {:command, key, message} → This tuple will match when a command is received
  • {:text, text, message} → This tuple will match when plain text is sent to the bot (check privacy mode)
  • {:regex, key, message} → This tuple will match if a regex is defined at the beginning of the module
  • {:location, location} → This tuple will match when a location message is received
  • {:callback_query, callback_query} → This tuple will match when a Callback Query is received
  • {:inline_query, inline_query} → This tuple will match when an Inline Query is received
  • {:edited_message, edited_message} → This tuple will match when a message is edited
  • {:message, message} → This will match any message that does not fit with the ones described above
  • {:update, update} → This tuple will match as a default handle

Execute code on initialization

The 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.

The 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.

Example of usage:

defmodule MyBot.Bot do
  @bot :my_bot

  use ExGram.Bot, name: @bot

  def init(opts) do
    ExGram.set_my_description!(description: "This is my description", bot: opts[:bot]) # with :bot
    ExGram.set_my_name!(name: "My Bot", token: opts[:token]) # with :token
    :ok
  end

  # ...
end

Sending files

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:

ExGram.send_document(chat_id, document_id)                                     # By document ID

ExGram.send_document(chat_id, {:file, "path/to/file"})                         # By local path

ExGram.send_document(chat_id, {:file_content, "FILE CONTENT", "filename.txt"}) # By content

This three ways of sending files works when the API has a file field, for example send_photo, send_audio, send_video, ...

Library Usage

Sometimes 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.

You 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:

ExGram.send_message("@my_channel", "Sending messages!!!")

Alternatively, 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:

ExGram.send_message("@my_channel", "Sending messages!!!", token: "BOT_TOKEN")

If you want to know how the low level API is designed and works, you can read the next section.

Low level API

All the models and methods are equal one to one with the models and methods defined on the Telegram Bot API Documentation!

Models

All the models are inside of the ExGram.Model module. You can see all the models in lib/ex_gram.ex file, for example User:

model(User, [
  {:id, :integer},
  {:is_bot, :boolean},
  {:first_name, :string},
  {:last_name, :string},
  {:username, :string},
  {:language_code, :string}
])

Also, 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:

>>> t ExGram.Model.User
@type t() :: %ExGram.Model.User{
  first_name: String.t(),
  id: integer(),
  is_bot: boolean(),
  language_code: String.t(),
  last_name: String.t(),
  username: String.t()
}

Methods

All the methods are inside of the ExGram module, they are like the documentation ones but in snake_case instead of camelCase.

If 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.

Also, 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:

>>> h ExGram.send_message

def send_message(chat_id, text, ops \\ [])

@spec send_message(
  chat_id :: integer() | String.t(),
  text :: String.t(),
  ops :: [
    parse_mode: String.t(),
    disable_web_page_preview: boolean(),
    disable_notification: boolean(),
    reply_to_message_id: integer(),
    reply_markup:
      ExGram.Model.InlineKeyboardMarkup.t()
      | ExGram.Model.ReplyKeyboardMarkup.t()
      | ExGram.Model.ReplyKeyboardRemove.t()
      | ExGram.Model.ForceReply.t()
    ]
) :: {:ok, ExGram.Model.Message.t()} | {:error, ExGram.Error.t()}

All 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.

For 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:

ExGram.get_updates(offset: 123, limit: 100)

Another 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:

ExGram.send_message("@rockneurotiko", "Hey bro! Checkout the ExGram library!", disable_notification: true)

Extra options

All the methods have three extra options:

  • debug: When true it will print the HTTP request response.
  • token: It will use this token for the request.
  • 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.

Note: Only one of token and bot must be used.

How it's made?

There is a Python script called extractor.py, it uses the 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.

This 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.

Custom types defined

  • :string -> String.t()
  • :int or :integer -> integer
  • :bool or :boolean -> boolean
  • :file -> {:file, String.t()}
  • {:array, t} -> [t]
  • Any ExGram.Model

Model macro

Parameters:

  1. Name of the model
  2. 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.

This macro is the simple one, just create a module with the first name passed and use the params to create the struct and typespecs.

Method macro

Parameters:

  1. Verb of the method (:get or :post)
  2. Name of the method as string, it will be underscored.
  3. The parameters of the method, this is a list of tuples, the tuples contains:
  • Name of the parameters
  • 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.
  • An optional third parameter (always :optional) to set that parameter as optional
  1. The type to be returned, it can be a model.

The 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.

These 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.

Creating your own updates worker

The 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.

But you can implement your own worker to retrieve the updates as you want!

The only specs are that start_link will receive {:bot, <pid>, :token, <token>}, the PID is where you should send the updates, and the token that your worker will be able to use to retrieve the updates.

Whenever you have an update ExGram.Model.Update, send it to the bot's PID like: {:update, <update>} with GenServer.call.

You can see the code of ExGram.Updates.Polling.

ex_gram's People

Contributors

alan-andrade avatar aus70 avatar indocomsoft avatar ironjanowar avatar kianmeng avatar prtngn avatar rockneurotiko avatar sega-yarkin avatar thehabbos007 avatar versilov avatar whitered avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ex_gram's Issues

Parsing JSON to proper types

I am working on adding proper types for Bot API data using Ecto embedded schemas. Then it would be possible to parse JSON straight to model structs, and also to validate data before making requests.
Before I start working on a pull request, I want to ask - are you open to adding this feature to ex_gram ?
I am going to use polymorphic_embed for generics.

Weird error ssl related

2019-03-29T17:30:45.320Z [error] GenServer #PID<0.295.0> terminating
** (FunctionClauseError) no function clause matching in ExGram.Updates.Polling.handle_info/2
    (ex_gram) lib/ex_gram/updates/polling.ex:21: ExGram.Updates.Polling.handle_info({:ssl_closed, {:sslsocket, {:gen_tcp, #Port<0.8>, :tls_connection, :undefined}, [#PID<0.292.0>, #PID<0.291.0>]}}, {:spotify_uri_bot, "AWESOME_TOKEN"})
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:ssl_closed, {:sslsocket, {:gen_tcp, #Port<0.8>, :tls_connection, :undefined}, [#PID<0.292.0>, #PID<0.291.0>]}}
State: {:spotify_uri_bot, "AWESOME_TOKEN"}

Happened to mi on this bot

Sometimes the bot does not work

Hello,

I am running the bot inside a phoenix project. I have added an application.ex file in my MyApp folder (NOT MyAppWeb folder) and the bot.ex is in same folder.

When i add a new command to bot.ex or change something, sometimes, that does not get executed. Sometimes it does.

Am I missing something?

Fix text matching in the doc

Hi

This command does not match
{:text, text} → This tuple will match when plain text is sent to the bot (check privacy mode)

I think it's this definition :)
{:text, text, message} → This tuple will match when plain text is sent to the bot (check privacy mode)

Improvements in README.md

As a newbie to Elixir, I realised the README.md could be organised better - changing the ordering of few sections + highlighting few more things.

Shall I send a pull request with requisite changes?

Better documentation

Will you be willing to take up a better documentation on lines of https://github.com/dwyl/elixir-auth-google

I love how above mentioned repo answers all basic questions like what why who how when etc.

Your input needed on :

  1. What is your pain point with present documentation ?
  2. Is there an awesome feature that has not been described in detail?
  3. If you can give a high level idea (maybe 3 steps) on how this library works.

Let me know. I am willing to contribute to better documentation.

[Tesla-Middlewares] Can not write functions in config

So... I did not make a release when testing the Tesla middlewares PR.

This error raises when a function is in a config file:
** (Mix) Could not read configuration file. It likely has invalid configuration terms such as functions, references, and pids. Please make sure your configuration is made of numbers, atoms, strings, maps, tuples and lists. Reason: {3, :erl_parse, ['syntax error before: ', 'Fun']}

As you can see the config requires a function in the case of the Tesla.Middleware.Retry

Problem with get_chat_members, get_chat_administrators, etc method.

Hi @rockneurotiko. When I call the get_chat_members function, I get the following error:

** (UndefinedFunctionError) function ExGram.Model.ChatMember.__struct__/0 is undefined or private
    (ex_gram 0.34.0) ExGram.Model.ChatMember.__struct__()
    (elixir 1.14.4) lib/kernel.ex:2354: Kernel.struct/3
    (elixir 1.14.4) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ex_gram 0.34.0) lib/ex_gram/macros/executer.ex:52: ExGram.Macros.Executer.execute_method/9

If I understand correctly, the issue is with the extractor?

Add description + initial callback

With bot api 6.6 we can set description and short description, it would be great to being able to setup that on use ExGram.Bot and the bot would set them on startup.

Also add a new callback that will be called at the startup.

Help with code needed - Tesla adapter `handle_result`

code from repo

https://github.com/rockneurotiko/ex_gram/blob/master/lib/ex_gram/adapter/tesla.ex#L53

question

Does this function get a call ever? The function above it would always match some status. So the flow enters the function define above L53.

concept check

In my understanding of pattern matching in function arguments -> if pattern matched, all other definitions (below) current one are ignored, right?

EDIT : concept was NOT ok. when {:ok, :true} does not match, function below is called.

suggestions - what am I missing?

Resolved (see Edit)

send_media_group/3 does not accept list of InputMediaPhoto

While trying to send the following media group:

[
  %ExGram.Model.InputMediaPhoto{
    caption: nil,
    caption_entities: nil,
    media: "https://sbermarket.ru/spree/products/303813/original/200585.jpg",
    parse_mode: nil,
    type: "photo"
  },
  %ExGram.Model.InputMediaPhoto{
    caption: nil,
    caption_entities: nil,
    media: "https://sbermarket.ru/spree/products/303813/original/200585.jpg",
    parse_mode: nil,
    type: "photo"
  }
]

I get this error:

** (ExGram.Error) Mandatory parameter types don't match: parameter 1 expected [array: [InputMediaAudio, InputMediaDocument, InputMediaPhoto, InputMediaVideo]] but got [%ExGram.Model.InputMediaPhoto{caption: nil, caption_entities: nil, media: "https://sbermarket.ru/spree/products/303813/original/200585.jpg", parse_mode: nil, type: "photo"}, %ExGram.Model.InputMediaPhoto{caption: nil, caption_entities: nil, media: "https://sbermarket.ru/spree/products/303813/original/200585.jpg", parse_mode: nil, type: "photo"}]
    (ex_gram 0.21.0) lib/ex_gram/macros.ex:105: ExGram.send_media_group!/3

The input seems correct, probably error is in type checker?

Catch errors

Hi. How I can catch some errors if I send message via answer?
Imagine, I send document with wrong id:

context |> answer_document("SOME_WRONG_ID")

and document not sended. And I want to send document via file_path.

context |> answer_document({:file, "/some/path/to/file.pdf"})

How I can do it? ))

Answer with invoice

Hi. How I can send answer via ExGram.Dsl.Answer with included invoice?
And maybe you have good example for invoice and payments process?

answer_inline_query return error

Trying to build an inline result leads to a weird error

message_text = %ExGram.Model.InputTextMessageContent{message_text: text}

result = %ExGram.Model.InlineQueryResultArticle{
  input_message_content: message_text,
  title: "Result 1",
  id: 1,
  type: "article"
}

ExGram.answer_inline_query(inline_query_id, [result]) |> inspect |> Logger.debug()

Here is the error:
{:error, "Some invariant of the method answerInlineQuery was not succesful, check the documentation"}

I checked the docs just in case and these are the objects we have to build to send an answer:

And of course the answerInlineQuery

Constructing a new poll

What methods are available for making new poll ?

I can see

    model(Poll, [
      {:id, :string},
      {:question, :string},
      {:options, {:array, PollOption}},
      {:total_voter_count, :integer},
      {:is_closed, :boolean},
      {:is_anonymous, :boolean},
      {:type, :string},
      {:allows_multiple_answers, :boolean},
      {:correct_option_id, :integer, :optional},
      {:explanation, :string, :optional},
      {:explanation_entities, {:array, MessageEntity}, :optional},
      {:open_period, :integer, :optional},
      {:close_date, :integer, :optional}
    ])

implemented. What's the corresponding function?

sending two messages in one command

Aim : when user sends /sum 4+5 bot should be able to reply with two messages

  1. answer to the present question (9)
  2. send a string "do you want this bot to solve more additions? "

--> bot waits for another /sum 50+60 like command.

What I tried?

def handle({:command, :sum, %{chat: %{id: chat_id}, text: text} = msg}, context) do

    # takes "4+5" as `text` and returns 9 (currently, only addition!)
    result =
      text
      |> String.split("+")
      |> Enum.map(fn x -> String.to_integer(x) end)
      |> Enum.sum()

    answer(context, Integer.to_string(result))
    # answer(context, "Would you like to solve more questions?")
    # handle({:command, :start, msg}, context)
  end

approaches that do not work

See the last two lines. both of the below approaches skip sending original answer (9)

  1. after replying with answer(context, Integer.to_string(result)) I tried to add another answer = did not work.
  2. Tried to call handle function which has :start message = did not work either!

suggestion for alternatives to send two or more replies on one command of user.

waiting...

Some complex types are incorrect

In the Bot API spec there are types such as "integer or string", that are treated incorrectly in ex_gram.
Example (chat_id is a string or an integer)

model(
  BotCommandScopeChat,
  [{:type, :string}, {:chat_id, :integer}],
  "Represents the scope of bot commands, covering a specific chat."
)

It's probably not an issue for somebody who is only using integers, but if somebody wants to use this spec for parsing the API JSON, it can be a big deal.

Documentation

Create documentation:

  • General framework usage
  • Middlewares
  • Use multiple bots
  • Tips & Tricks ?

Problem with send_media_group

I'll try to send media_group with photos or documents and get same error:

      ExGram.send_media_group(
        user.telegram_id,
        media: [
          %ExGram.Model.InputMediaDocument{
            type: "document",
            media: "BQACAgIAAxkBAAEGtFRkU7LYwc22u650vKUZ9hzUAAEl13QAAnAuAAKvw6FKVOYAAbzuWHewLwQ"
          },
          %ExGram.Model.InputMediaDocument{
            type: "document",
            media: "BQACAgIAAxkBAAEBQJpkJtrMNEdgDH1ebZGm4Gpd8GB1EgACbycAAsVbOUlzhwdVzy5QHS8E"
          }
        ],
        bot: Bot.bot()
      )
** (FunctionClauseError) no function clause matching in ExGram.Macros.Checker.check_type/2
    (ex_gram 0.34.0) lib/ex_gram/macros/checker.ex:42: ExGram.Macros.Checker.check_type(InputMediaAudio, {:media, [%ExGram.Model.InputMediaDocument{type: "document", media: "BQACAgIAAxkBAAEGtFRkU7LYwc22u650vKUZ9hzUAAEl13QAAnAuAAKvw6FKVOYAAbzuWHewLwQ", thumbnail: nil, caption: nil, parse_mode: nil, caption_entities: nil, disable_content_type_detection: nil}, %ExGram.Model.InputMediaDocument{type: "document", media: "BQACAgIAAxkBAAEBQJpkJtrMNEdgDH1ebZGm4Gpd8GB1EgACbycAAsVbOUlzhwdVzy5QHS8E", thumbnail: nil, caption: nil, parse_mode: nil, caption_entities: nil, disable_content_type_detection: nil}]})

How to create many bots

Hello, tell me how to create many bots but with one logic? I have clients and each client has a small question builder for the bot.

Webhook timeout when calling long-running funcs

I'm using a locally-hosted Large Language Model to generate bot replies, and sometimes a few seconds are needed to return a 200 OK to the webhook, which leads to the following error:

** (EXIT) time out
    (elixir 1.15.0) lib/gen_server.ex:1074: GenServer.call/3
    (ex_gram 0.40.0) lib/ex_gram/updates/webhook.ex:34: ExGram.Updates.Webhook.handle_cast/2

It would be nice to be able to increase the timeout from 5000ms to some longer interval or to return a 200 OK before calling long-running functions: what's the best way to do it?

Thanks.

writing send_poll Protocol - help needed.

Hello,

Objective:

To make send_poll pipe-able I am trying to make following changes to library.

so I want to use it like
context |> answer('anyhting') |> send_poll(id,"question",['1','2'],type:"quiz",correct_option_id: 0)

what I did?

  1. add send_poll.ex protocol implementation responses folder
  2. adding SendPoll as import alias in dsl.ex
  3. adding send_poll function in dsl.ex

Where I need help?

  1. Do I need to touch any other file?
  2. in answer Here -- What does add_answer do?

I'll create a PR once I get a response from you and sort things out!

How to run custom code at bot startup?

I'd like to run set_my_command, set_my_description, set_my_name and some other initialization code at bot startup. What's the best way to do it?
Thanks!

[Bot answer] Token not included in api requests for answers

I set up a test project https://github.com/thehabbos007/exgram_error_example
The test project listens for a /start command, and needs a TELEGRAM_TOKEN environment variable at runtime.

The problem seems to be, that when I call answer in a given context from a message, the following error occurs (note, I use tesla with gun)

20:43:20.777 [info]  POST https://api.telegram.org/bot/sendMessage -> 404 (163.433 ms)
20:43:20.777 [debug]
>>> REQUEST >>>
Content-Type: application/json
%{chat_id: 392938231, text: "Hi!"}

<<< RESPONSE <<<
%{description: "Not Found", error_code: 404, ok: false}

The message is sent, but the token is not included in the post request.

With some tinkering around, it seems that moving the token fetching down in the macro fixes the problem, since some tokens are filtered away.

token = ExGram.Token.fetch(ops)
debug = Keyword.get(ops, :debug, false)
# Remove not valids
ops = Keyword.take(ops, unquote(opt_par))

However, this will likely cause problems.

I believe this is caused by the bot being registered in the bot registry without a token, so it matches on this case

{nil, bot} ->
registry = Keyword.get(ops, :registry, @registry)
[{_, token} | _] = Registry.lookup(registry, bot)
token

and then returns no actual token.

To confirm, looking into the registry returns

iex(1)> Registry.lookup(Registry.ExGram, :bot_example)
[{#PID<0.229.0>, nil}]

So I think this can be traced all the way back to how each bot is registered in the supervision tree, if a user of the framework does not specify a token, this will end up failing (i am assuming).

Maybe we should try to think up some defaults? Perhaps checking if the token is set in the init of the bot supervisor and default it to the config if not? I am still a newbie in elixir, so I'm open to ideas 😄

Relax hackney semver requirement

Currently, in mix.exs, hackney semver requirement is ~> 1.12.1, which means it requires the same major and minor version. Currently, hackney version is already at 1.15.1.

From what I see, hackney API is quite stable now, and according to semver, minor version bump means functionalities were added in a backwards-compatible manner, so I think it should be fine to relax the hackney requirement to ~> 1.12 instead, which means it only requires the same major version number.

This is a problem right now, e.g. excoveralls requires ~> 1.13.

Continuous timeout error when polling

When the bot is running and it's idle, I can see this:

[error] GET https://api.telegram.org/botXXX/getUpdates -> error: "timeout" (15131.153 ms)
[debug] 
>>> REQUEST >>>
Query: timeout: 50
Query: offset: -1
Query: limit: 100

Content-Type: application/json

(no body)

<<< RESPONSE ERROR <<<
"timeout"

Looks like after 15 seconds, there was no information to be updated and it's performing a timeout but as an error while this kind of timeout should be only "info" instead of "error" because it's expected. The result is that I'm plenty of errors because most of the time the bot isn't doing anything every 15 seconds :-/

Maybe it's something at the Tesla adapter that I could configure but not sure at the moment. Any ideas? Thanks in advance.

using local bot server

Is there a way to configure the bot to use local server right from start i.e. iex -S mix run --no-halt?

Release version 0.6.0

Currently version 0.5.0-rc3 is up but the README still says:

image

So! Time to update :D

Decouple dispatching logic from its process

Currently dispatcher is a GenServer, and you have to dispatch using GenServer API.
I think it would make sense to move the dispatching logic to a separate module, so that developer could use the dispatcher without spawning a separate process. This would help people use the library on a deeper level if they have custom requirements (e.g. using webhook message sourcing).
People who want to keep on using the dispatcher process would be able to continue doing so.
What do you think ? Does it make sense ?

setChatMenuButton error

We have auto generated method:

  method(
    :post,
    "setChatMenuButton",
    [{chat_id, [:integer], :optional}, {menu_button, [MenuButton], :optional}],
    true,
    "Use this method to change the bot's menu button in a private chat, or the default menu button. Returns True on success."
  )

But menu_button must be MenuButtonCommands, MenuButtonWebApp, MenuButtonDefault.

I don't understand, why Dsl.edit not working with ReplyKeyboardMarkup

I try to edit message:

    context
    |> edit(
      :inline,
      "Give your phone",
      reply_markup: %ReplyKeyboardMarkup{
        keyboard: [
          [
            %KeyboardButton{
              text: "📲 Get phone",
              request_contact: true
            }
          ],
          [
            %KeyboardButton{
              text: "📵 Stop"
            }
          ]
        ],
        resize_keyboard: true,
        one_time_keyboard: true
      }
    )

and this is not working. But if I send reply_markup: %InlineKeyboardMarkup all ok.

[Regex] Problems compiling project when using regexes

This was on 0.8
I was trying to see if i could make regexes work, when compiling my bot with regex("test", :test) in the module, I the following error

** (CompileError) lib/bot/bot.ex: invalid quoted expression: ~r/test/

Please make sure your quoted expressions are made of valid AST nodes. If you would like to introduce a value into the AST, such as a four-element tuple or a map, make sure to call Macro.escape/1 before
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3
    /mnt/c/Users/Ahmad/Desktop/dev/playground/bot/lib/bot/bot.ex:1: ExGram.Middleware.Builder.__before_compile__/1

Calling Macro.escape on the Regex.compile! function in

@regexes [regex: Regex.compile!(unquote(regex)), name: unquote(name)]
seemed to fix this.

I'll gladly make a PR and update some of the readme for regexes if this change won't break anything 😄

Issue in `send_document` options

Why do we need to add 'file content' ourselves in this file?

ExGram.send_document(chat_id, {:file_content, "FILE CONTENT", "filename.txt"}) # By content

I don't understand this function.

Allow to configure "allowed_updates" parameter in polling mode

Would be great if it will be allowed to configure "allowed_updates" parameter in polling mode.

By default Telegram not sending "chat_member", "message_reaction" and "message_reaction_count" updates and the only way to get it is to specify it in allowed_updates parameter to getUpdates (called by the framework under the hood).

Multiform requests not working

I'm trying to upload a photo from a bot.

Code looks like:

ExGram.send_photo 6666666, "photo.jpg"

I receive the following error:

{:error,                                                                                                                                                                                              [243/1927]
 %ExGram.Error{
   code: :response_status_not_match,
   message: "{\"ok\":false,\"error_code\":400,\"description\":\"Bad Request: unsupported URL protocol\"}",
   metadata: nil
 }}

After some debugging, I realized the request isn't being multiformed properly.
I also saw this might be a known issue since some type validations have a"#TODO" note.

For quickfix I was able to work around it by modifying macros.ex ~LOC 382.

# Extract the value and part name (it can be from parameter or ops)
...
              {vn, partname} =
                case Enum.at(unquote(multi_full), 0) do
                  {v, p} -> {v, p}
                  keyw -> {ops[keyw], Atom.to_string(keyw)}
                end
                
              # Hack for now
               vn2 = {:file, vn}

              case vn2 do
                {:file, path} ->
                  # It's a file, let's build the multipart data for maxwell post
                  disposition = {"form-data", [{"name", partname}, {"filename", path}]}
                  # IO.puts("disposition: #{disposition}")
                  # File part
                  fpath = {:file, path, disposition, []}
...

Notice the vn2.

I'm just beginning with Elixir and I haven't figured out the best approach for this fix. Any ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.