GelotvBot
gelotv_bot is an Elixir/OTP library for sending the same chat command or
alert to multiple livestream chats through supervised bot instances.
The library is platform-neutral. Twitch, YouTube, Kick, or private chat
integrations are implemented as adapters behind GelotvBot.Adapter; the core
library handles message normalization, concurrent fan-out, shared rate-limit
coordination, supervision, and signed metadata helpers.
Installation
When published, add the package to your dependencies:
def deps do
[
{:gelotv_bot, "~> 0.1"}
]
end
Core Concepts
GelotvBot.Targetdescribes one platform/channel destination.GelotvBot.Messagestores the visible chat body plus structured metadata.GelotvBot.Adapteris the behaviour for platform-specific send code.GelotvBot.Botis a supervised named bot instance.GelotvBot.RateLimitercoordinates per-target token buckets across bot instances.GelotvBot.RetryPolicyretries transient adapter errors such as platform rate-limit responses with bounded backoff.
Example
targets = [
%GelotvBot.Target{
platform: :twitch,
channel: "gelotv",
adapter: MyApp.TwitchAdapter,
rate_limit: [limit: 20, interval: 30_000, burst: 5]
},
%GelotvBot.Target{
platform: :youtube,
channel: "gelotv-live",
adapter: MyApp.YouTubeAdapter,
rate_limit: [limit: 60, interval: 60_000, burst: 10]
},
%GelotvBot.Target{
platform: :kick,
channel: "gelotv",
adapter: MyApp.KickAdapter
}
]
{:ok, _pid} = GelotvBot.start_bot(:donation_alerts, targets: targets)
GelotvBot.send(:donation_alerts, "Thanks Ana for the donation!")
Retries can be configured per bot or per send:
GelotvBot.start_bot(:donation_alerts,
targets: targets,
retry: [max_attempts: 3, base_backoff: 250, max_backoff: 5_000]
)
Multiple commands can be dispatched in one call:
GelotvBot.send_many(:donation_alerts, [
GelotvBot.Command.new(:donation, "Thanks Ana!", %{amount: 10}),
GelotvBot.Command.new(:follow, "Welcome Bruno!")
])
For the direct no-bot-process path, use one function for one or many targets and one or many messages:
GelotvBot.dispatch(targets, [
"First live message",
GelotvBot.Command.new(:follow, "Welcome Bruno!")
])
Bot instances are independent supervised processes, but the default
GelotvBot.RateLimiter is shared by the application, so two instances sending
to the same target coordinate against the same bucket.
Targets can be replaced while a bot is running:
GelotvBot.put_targets(:donation_alerts, updated_targets)
Running bot instances can be listed and stopped:
GelotvBot.list_bots()
GelotvBot.stop_bot(:donation_alerts)
For direct one-call bot sends without managing a named bot process, discover active live chats and broadcast to all of them:
specs = [
%{
platform: :twitch,
channels: ["gelotv"],
credentials: %{access_token: twitch_token, client_id: twitch_client_id, sender_id: twitch_sender_id}
},
%{
platform: :youtube,
credentials: %{access_token: youtube_token}
},
%{
platform: :kick,
channels: ["gelotv"],
credentials: %{access_token: kick_token}
}
]
GelotvBot.send_live(specs, [
GelotvBot.Command.new(:donation, "Thanks Ana!"),
GelotvBot.Command.new(:follow, "Welcome Bruno!")
])
send_live/3 and broadcast_live/3 discover active lives, convert them into
Targets, and use the same direct dispatch path as every other multi-live send.
They accept either a single message or a list of messages.
Token helpers cover the OAuth flows commonly needed before bot sends:
{:ok, twitch_token} =
GelotvBot.token(:twitch, :client_credentials, %{
client_id: client_id,
client_secret: client_secret
})
{:ok, youtube_token} =
GelotvBot.token(:youtube, :refresh, %{
client_id: client_id,
client_secret: client_secret,
refresh_token: refresh_token
})
{:ok, kick_token} =
GelotvBot.token(:kick, :refresh, %{
client_id: client_id,
client_secret: client_secret,
refresh_token: refresh_token
})
Built-In Platform Adapters
The package includes HTTP adapters for current chat-send APIs:
GelotvBot.Adapters.Twitch:POST https://api.twitch.tv/helix/chat/messagesGelotvBot.Adapters.YouTube:POST https://www.googleapis.com/youtube/v3/liveChat/messages?part=snippetGelotvBot.Adapters.Kick:POST https://api.kick.com/public/v1/chat
The application is still responsible for OAuth flows and token refresh. Credentials are passed on the target:
%GelotvBot.Target{
platform: :twitch,
channel: "gelotv",
adapter: GelotvBot.Adapters.Twitch,
credentials: %{
access_token: "...",
client_id: "...",
broadcaster_id: "...",
sender_id: "..."
}
}
The built-in adapters validate messages before making HTTP calls. Blank messages are rejected, and Twitch/Kick messages are capped at 500 characters to match their chat-send APIs.
Typed helpers cover common bot-live API calls while the generic request layer remains available for the rest of each platform:
GelotvBot.APIs.Twitch.streams(twitch_credentials, params: [user_login: "gelotv"])
GelotvBot.APIs.Twitch.chat_settings(twitch_credentials, broadcaster_id, moderator_id)
GelotvBot.APIs.YouTube.live_broadcasts(youtube_credentials,
params: [broadcastStatus: "active", mine: true]
)
GelotvBot.APIs.YouTube.live_chat_messages(youtube_credentials, live_chat_id)
GelotvBot.APIs.Kick.channels(kick_credentials, params: [slug: ["gelotv"]])
Full Platform API Access
The high-level adapters are intentionally small, but the library also exposes generic dependency-free API clients for Twitch, YouTube, and Kick:
GelotvBot.APIs.TwitchGelotvBot.APIs.YouTubeGelotvBot.APIs.Kick
These clients build authenticated requests for their platform base APIs and can call any current or future endpoint by path, method, params, headers, and body. Responses are returned as raw HTTP status, headers, and body so callers can work with the complete platform surface without waiting for typed wrappers.
GelotvBot.APIs.Twitch.get(
"/streams",
%{access_token: token, client_id: client_id},
params: [user_login: "gelotv"]
)
GelotvBot.APIs.YouTube.get(
"/videos",
%{access_token: token, api_key: api_key},
params: [part: "snippet", id: video_id]
)
GelotvBot.APIs.Kick.post(
"/chat",
%{access_token: token},
%{content: "Hello chat"}
)
If you want parsed JSON without giving up raw response data, use the decoded
variants. They preserve :status, :headers, and :body, then add
:decoded_body. Successful empty responses such as 204 No Content return
decoded_body: nil:
{:ok, response} =
GelotvBot.APIs.Twitch.get_decoded(
"/streams",
%{access_token: token, client_id: client_id},
params: [user_login: "gelotv"]
)
response.decoded_body["data"]
Absolute URLs are also accepted for platform-adjacent endpoints such as OAuth:
GelotvBot.APIs.Twitch.post(
"https://id.twitch.tv/oauth2/token",
%{},
%{client_id: client_id, client_secret: client_secret, grant_type: "client_credentials"},
body_format: :form
)
All of this uses the built-in :httpc client by default and does not require a
runtime HTTP or JSON dependency. Applications can inject another module that
implements GelotvBot.HTTPClient.
For APIs that need upload or custom content bodies, use body_format: :raw and
set content_type:
GelotvBot.APIs.YouTube.post(
"https://www.googleapis.com/upload/youtube/v3/videos",
%{access_token: token},
raw_video_bytes,
params: [uploadType: "media", part: "snippet"],
body_format: :raw,
content_type: "video/mp4"
)
If you want one function for all platforms, use GelotvBot.api_request/5:
GelotvBot.api_request(:twitch, :get, "/streams", credentials,
params: [user_login: "gelotv"]
)
GelotvBot.api_request(:youtube, :get, "/videos", credentials,
params: [part: "snippet", id: video_id]
)
GelotvBot.api_request(:kick, :post, "/chat", credentials,
body: %{content: "Hello chat"}
)
Use GelotvBot.api_request_decoded/5 for the same cross-platform request path
with JSON decoding:
GelotvBot.api_request_decoded(:twitch, :get, "/streams", credentials,
params: [user_login: "gelotv"]
)
For paginated endpoints, use paginate/3 on a platform module or
GelotvBot.api_paginate/4. Twitch cursor pagination and YouTube page-token
pagination are built in; Kick or custom endpoints can pass a next callback:
GelotvBot.api_paginate(:twitch, "/streams", credentials,
params: [first: 100]
)
GelotvBot.api_paginate(:youtube, "/videos", credentials,
params: [part: "snippet"]
)
GelotvBot.api_paginate(:kick, "/channels", credentials,
next: fn
%{"next" => next} when is_binary(next) -> [page: next]
_ -> nil
end
)
Custom Adapter Contract
defmodule MyApp.TwitchAdapter do
@behaviour GelotvBot.Adapter
@impl true
def send_message(target, message, _opts) do
# Use official platform APIs and authenticated credentials here.
# Return :ok, {:ok, response}, or {:error, reason}.
# For platform throttles, return {:error, {:rate_limited, retry_after_ms}}.
end
end
Metadata
Use out-of-band metadata for the cleanest chat UX. The chat body remains exactly what viewers see, while routing/audit fields stay on the message struct:
message =
GelotvBot.Message.new("Thanks Ana!", %{
event: "donation",
source: "gelotv-bot"
})
Dispatch results include the original message, so applications can persist
metadata next to platform responses or returned message IDs without adding
hidden characters to public chat text.
If metadata must travel in the chat message, GelotvBot.Metadata.attach/2
creates a visible, signed token:
message =
message
|> GelotvBot.Metadata.attach(secret: System.fetch_env!("GELOTV_BOT_SECRET"))
For integrations that require an invisible carrier, encode_invisible/2 returns
a signed payload encoded with the zero-width codec:
U+200B ZERO WIDTH SPACErepresents bit0.U+200C ZERO WIDTH NON-JOINERrepresents bit1.- Payload bytes are encoded most-significant bit first.
encoded = GelotvBot.Metadata.encode_invisible(%{event: "donation"}, secret)
{:ok, decoded} = GelotvBot.Metadata.decode_invisible(encoded, secret)
The same standardized codec can carry arbitrary binary strings:
encoded = GelotvBot.Metadata.encode_zero_width("hello")
{:ok, "hello"} = GelotvBot.Metadata.decode_zero_width(encoded)
Legal and Privacy Notice
gelotv_bot is a software library. Installing or using the package does not
create any hosted service relationship with GeloTV, and the library does not
send chat messages, credentials, metadata, tokens, analytics, logs, or telemetry
to GeloTV by itself.
Applications using this library decide which platforms receive messages, which credentials are used, what metadata is attached, and how sent-message records are stored. Operators of those applications are responsible for:
- complying with Twitch, YouTube, Kick, and any other platform terms and API rules;
- obtaining any required permissions from streamers, moderators, viewers, and message recipients;
- choosing whether metadata is sent out-of-band, visibly in-band, or through the zero-width codec;
- protecting access tokens, secrets, and chat logs;
- complying with privacy, consumer-protection, advertising, donation, and other laws that apply to their own use case and jurisdiction.
The names Twitch, YouTube, and Kick refer to third-party platforms. This
package is not endorsed by, sponsored by, or affiliated with those platforms.
GeloTV is not responsible for messages, metadata, automations, accounts,
credentials, moderation decisions, bans, platform enforcement, or legal
consequences caused by applications built with this library. Review the
Apache-2.0 license and NOTICE file before distributing software based on this
package.
Development
mix deps.get
mix test
mix format
MIX_ENV=docs mix docs
mix hex.build
Publishing Notes
The Mix project includes package metadata for Hex. Before publishing, update
the repository URL and maintainer information in mix.exs, then run:
mix hex.build
mix hex.publish
License: Apache-2.0.