cronokirby / alchemy

A discord library for Elixir
MIT License
152 stars 34 forks source link

[feature | breaking] Support for handling many bots at once #106

Open curz46 opened 3 years ago

curz46 commented 3 years ago

Right now, alchemy has a global architecture intended to support a single bot account. But there are many cases where a user would want to load and interface with many bots at a time, all in the same application instance.

Prerequisites

Problems

Cogs

defmodule MyBot do use Application

def start(_type, _args) do run = Client.start("token") use MyCommands run end end

- Suggested usage (also moving away from a magical Cogs)
```elixir
defmodule MyCommands do
  def hello(message, [name], _context = %{client: client}) do
    Alchemy.Client.reply(client, message, "Hello " <> name)
  end

  def hello(_message, _args, _context) do
    :noop
  end

  def register_commands(client) do
    Alchemy.Commands.add_command(client, "hello", &Commands.hello/3)
  end
end

defmodule MyBot do
  use Application

  alias Alchemy.Client

  def start(_type, _args) do
    with {:ok, client} <- Client.start("token") do
      # if we need custom keys in context
      Alchemy.Commands.put_context(client, :some_key, :some_value)
      MyCommands.register_commands(client)
      {:ok, client}
    end
  end
end

Events

defmodule MyBot do use Application

def start(_type, _args) do run = Client.start("token") use MyEvents run end end


- Suggested usage (also with proposed events usage from #71)
```elixir
defmodule MyEvents do
  def on_message(_event = %MessageCreateEvent{channel_id: channel_id, message: message}, _context = %{client: client}) do
    %{content: content} = message
    Alchemy.Client.send_message(client, channel_id, "Received: " <> content)
  end

  def register_handlers(client) do
    Alchemy.Events.add_handler(client, :message_create, &on_message/2)
  end
end

defmodule MyBot do
  use Application

  def start(_type, _args) do
    with {:ok, client} <- Client.start("token") do
      # if we need custom keys in context
      Alchemy.Events.put_context(client, :some_key, :some_value)
      MyEvents.register_handlers(client)
      {:ok, client}
    end
  end
end
curz46 commented 3 years ago

These changes would make more sense to make after #71 and Cogs is changed. It's not clear whether or not Cache would be affected by this change since we may want Clients to be able to share a Cache. With three bots, Alchemy would be receiving the same events three times (if the bots were in the same servers), which I think is an argument to make caches client-specific.

cronokirby commented 3 years ago

Yeah, this is probably the most longstanding flaw in the library, and something I've always wondered how to address in a clean way.

I remember kind of realizing that passing around the current client was pretty much unavoidable, or at the very least, you need some kind of way of knowing which API key to be using at any time, since you've got multiple floating around.

Maybe this redesign would be a candidate for a 1.0?

But yeah, this seems like a good idea, and a bit cleaner too.

I think you can design the Cache with this in mind, and have much problems. All requests would flow through the same infrastructure, just tagged with the API key, so you could use a universal cache for requests.

For guild events in theory you can share a cache, although one issue might be that one bot could see things in the cache that it would otherwise not have permission to see if it asked for them globally. This would give non-deterministic results at that point.

One solution to that problem is to simply separate things out into multiple caches, then you would never have problems of that sort.

I think that's one big thing that would be need to modified in the cache, as I think if you just used the current cache with multiple bots you could get into weird states where a bot can sometimes see things it's not supposed to have access to. But you could easily just dynamically spin up multiple caches, and route to the correct one based on which bot you're dealing with.