kappelas

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2026 License: MIT Imports: 15 Imported by: 0

README

kappelas-sdk-go

Go Reference Go version GitHub

Official Go SDK for the Kappela messaging platform.
Build bots and personal automations — send messages, handle events, manage chats.


Table of contents


Prerequisites

You need a bot token from BotMother, the official Kappela bot manager.

  1. Open Kappela and start a conversation with BotMother
  2. Follow the instructions to create a bot
  3. BotMother gives you a token — keep it secret, it gives full control over your bot

For personal automation (sending messages as yourself), generate an API key from your Kappela account settings (sk_...).


Install

go get github.com/Arnel7/kappelas-sdk-go

Requires Go 1.21+.


Quick start

Bot
package main

import (
    "context"

    "github.com/Arnel7/kappelas-sdk-go"
)

func main() {
    ctx := context.Background()
    bot := kappelas.NewBot("YOUR_BOT_TOKEN")

    bot.OnMessage(func(msg *kappelas.Message) {
        bot.Reply(ctx, msg, "Echo: "+*msg.Text)
    })

    bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) {
        bot.Reply(ctx, cb, "Tu as cliqué : "+cb.CallbackData)
    })

    bot.Start()
    select {} // keep alive
}
Personal automation
me := kappelas.NewUser("sk_...")

me.OnMessage(func(msg *kappelas.Message) {
    if msg.Text != nil {
        fmt.Printf("[%d] %s\n", msg.ChatID, *msg.Text)
    }
})

me.Start()
select {}

Events — WebSocket vs Webhook

Mode Method Best for
WebSocket bot.Start() Development, local scripts
Webhook bot.Webhooks.Set() + bot.HandleWebhook() Production servers

The same OnMessage and OnCallbackQuery handlers work in both modes — no code change needed when switching.

WebSocket (development)
bot := kappelas.NewBot("YOUR_BOT_TOKEN")

bot.OnMessage(func(msg *kappelas.Message) { /* … */ })
bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) { /* … */ })

bot.Start()   // non-blocking, auto-reconnects on disconnect
select {}
Webhook (production)
import (
    "io"
    "net/http"

    "github.com/Arnel7/kappelas-sdk-go"
)

bot := kappelas.NewBot("YOUR_BOT_TOKEN")

// register once
ctx := context.Background()
bot.Webhooks.Set(ctx, kappelas.SetWebhookParams{
    URL: "https://your-server.com/kappela-webhook",
})

bot.OnMessage(func(msg *kappelas.Message) { /* … */ })
bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) { /* … */ })

http.HandleFunc("/kappela-webhook", func(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    bot.HandleWebhook(body)
    w.WriteHeader(http.StatusOK)
})

http.ListenAndServe(":8080", nil)

Do not call bot.Start() in webhook mode.

Event reference
Method Signature Description
OnMessage func(*Message) Incoming message of any type
OnCallbackQuery func(*CallbackQuery) Inline button clicked by a user
OnConnected func() WebSocket connected or reconnected
OnDisconnected func(code int, reason string) WebSocket disconnected
OnError func(error) Connection or transport error

WebSocket reconnection — by default the SDK retries up to 12 times with exponential back-off. Override with WithWSMaxRetries(n). When retries are exhausted OnDisconnected fires with code 1006 and reason "max retries reached".


bot.Reply() — convenience shorthand

Reply sends a text reply without having to repeat ChatID and ReplyToID manually.

  • Called with a *Message — sets ReplyToID automatically (shows a quote banner in the chat).
  • Called with a *CallbackQuery — sends to the same chat, no quote banner (callback queries have no message ID).
bot.OnMessage(func(msg *kappelas.Message) {
    ctx := context.Background()

    // Simple reply
    bot.Reply(ctx, msg, "Reçu 👍")

    // With an inline keyboard
    bot.Reply(ctx, msg, "Choisis une option :", kappelas.SendMessageParams{
        ReplyMarkup: kappelas.InlineKeyboard{
            InlineKeyboard: [][]kappelas.InlineKeyboardButton{{
                {Text: "✅ Oui", CallbackData: ptr("yes")},
                {Text: "❌ Non", CallbackData: ptr("no")},
            }},
        },
    })
})

bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) {
    ctx := context.Background()
    bot.Reply(ctx, cb, "Tu as cliqué : "+cb.CallbackData)
})

ChatID, ReplyToID, and Text in the optional SendMessageParams are filled automatically — you only need to set extra fields like ReplyMarkup or DeletePrevious.


Message fields
bot.OnMessage(func(msg *kappelas.Message) {
    msg.ID               // int64         — unique message ID
    msg.ChatID           // int64         — conversation ID
    msg.ChatType         // *ChatType     — "private" | "group" | "channel" (may be nil for history)
    msg.SenderID         // *string       — UUID of the sender (nil for system messages)
    msg.Type             // MessageType   — "text" | "image" | "video" | "audio" | "document" | …
    msg.Text             // *string       — text content (nil for media-only messages)
    msg.MediaID          // *string       — server-side media ID
    msg.ExtraData        // json.RawMessage — inline keyboard payload (when attached)
    msg.Status           // MessageStatus — "sent" | "delivered" | "read"
    msg.CreatedAt        // int64         — Unix timestamp (seconds)
    msg.EditedAt         // *int64        — last edit time, or nil
    msg.DeletedAt        // *int64        — deletion time, or nil
    msg.ReplyToID        // *int64        — ID of the message being replied to
    msg.ReplyToSnapshot  // *ReplySnapshot — snapshot of the replied-to message
    msg.Mentions         // []string      — UUIDs of mentioned users
    msg.SenderName       // *string       — display name in groups/channels (nil in private)
    msg.SenderAvatarURL  // *string       — avatar URL of the sender
    msg.ExpiresAt        // *int64        — expiry time for ephemeral messages
})

MessageType values

Value Description
"text" Plain text message
"image" Photo
"video" Video
"audio" Audio file
"document" Generic file
"sticker" Sticker
"poll" Poll
"location" Location pin
"contact" Contact card
"system" System notification (member joined, etc.)

CallbackQuery fields
bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) {
    cb.ChatID          // int64   — chat where the button was clicked
    cb.SenderID        // string  — UUID of the user who clicked
    cb.SenderNom       // *string — display name (e.g. "Arnel LAWSON")
    cb.SenderUsername  // *string — username (e.g. "arnell")
    cb.CallbackData    // string  — value set on the button
    cb.SentAt          // int64   — Unix timestamp (seconds)
})

Clicks are deduplicated server-side — your handler fires exactly once per click.


⚠️ SenderName vs SenderNom

These two fields look similar but come from different event types:

Field Event type Notes
msg.SenderName *Message Display name in groups/channels. Nil in private chats.
cb.SenderNom *CallbackQuery Display name of the user who clicked the button.

Copy-pasting a handler from OnMessage to OnCallbackQuery (or vice-versa) gives a compile error — which is intentional.


API reference

Constructor options
kappelas.NewBot(token string, opts ...BotOption) *Bot
Option Default Description
WithBaseURL(url) https://api.kappelas.com Override API base URL
WithMaxRetries(n) 2 HTTP retry count on 429 / 5xx
WithTimeout(d) 30s Per-request timeout
WithWSMaxRetries(n) 12 Max WebSocket reconnect attempts
kappelas.NewUser(apiKey string, opts ...UserOption) *User
Option Default Description
WithUserBaseURL(url) https://api.kappelas.com Override API base URL
WithUserMaxRetries(n) 2 HTTP retry count on 429 / 5xx
WithUserTimeout(d) 30s Per-request timeout
WithUserWSMaxRetries(n) 12 Max WebSocket reconnect attempts
bot := kappelas.NewBot("token",
    kappelas.WithTimeout(10 * time.Second),
    kappelas.WithMaxRetries(3),
)

messages
Messages.Send(ctx, params)(*SendResult, error)
yes, no := "yes", "no"
replyTo := int64(123)

result, err := bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID:    42,
    Text:      "Bonjour !",
    ReplyToID: &replyTo,          // optional — shows a quote banner
    ReplyMarkup: kappelas.InlineKeyboard{
        InlineKeyboard: [][]kappelas.InlineKeyboardButton{{
            {Text: "Oui", CallbackData: &yes},
            {Text: "Non", CallbackData: &no},
        }},
    },
})
// → &SendResult{MessageID: …, CreatedAt: …}

Pass DeletePrevious: true to automatically remove the previous message from this bot in the same chat before sending.

Messages.SendPhoto(ctx, params)(*SendMediaResult, error)
data, _ := os.ReadFile("banner.png")
result, err := bot.Messages.SendPhoto(ctx, kappelas.SendMediaParams{
    ChatID:  42,
    File:    kappelas.FileInput{Data: data, Filename: "banner.png", ContentType: "image/png"},
    Caption: "Voici notre bannière !",
})
// → SendMediaResult{MessageID, CreatedAt, MediaID}
Messages.SendVideo / SendDocument / SendAudio(*SendMediaResult, error)

Same shape — pass the appropriate FileInput.

Messages.SendCarousel(ctx, params)(*SendCarouselResult, error)
btn := "Voir"
bot.Messages.SendCarousel(ctx, kappelas.SendCarouselParams{
    ChatID: 42,
    Text:   "Choisissez un produit :",
    Carousel: []kappelas.CarouselCard{
        {ID: "p1", Title: "Produit A", Subtitle: ptr("9 900 FCFA"), ButtonText: &btn},
        {ID: "p2", Title: "Produit B", Subtitle: ptr("19 900 FCFA"), ButtonText: &btn},
    },
    QuickReplyButtons: []kappelas.ScrollKeyboardButton{
        {Text: "Voir plus"}, {Text: "Annuler"},
    },
})

When a user clicks a carousel card button, a CallbackQuery fires with CallbackData set to the card's ID ("p1", "p2", …).

Messages.Edit(ctx, params)(*EditMessageResult, error)
// Edit text only
bot.Messages.Edit(ctx, kappelas.EditMessageParams{
    ChatID: 42, MessageID: 123, NewText: "Mis à jour !",
})

// Edit inline keyboard only (keep existing text)
import "encoding/json"
done := "done"
kb, _ := json.Marshal(kappelas.InlineKeyboard{
    InlineKeyboard: [][]kappelas.InlineKeyboardButton{
        {{Text: "Terminé ✅", CallbackData: &done}},
    },
})
bot.Messages.Edit(ctx, kappelas.EditMessageParams{
    ChatID: 42, MessageID: 123, NewExtraData: kb,
})

// Remove the keyboard entirely
bot.Messages.Edit(ctx, kappelas.EditMessageParams{
    ChatID: 42, MessageID: 123, NewExtraData: json.RawMessage("null"),
})
// → EditMessageResult{Edited: true, MessageID: 123}
Messages.SendTyping(ctx, params)(*TypingResult, error)
bot.Messages.SendTyping(ctx, kappelas.SendTypingParams{ChatID: 42})        // show
f := false
bot.Messages.SendTyping(ctx, kappelas.SendTypingParams{ChatID: 42, IsTyping: &f}) // hide
Messages.Delete(ctx, params)(*DeleteResult, error)
bot.Messages.Delete(ctx, kappelas.DeleteMessageParams{ChatID: 42, MessageID: 123})
// → DeleteResult{Deleted: true}

chats
Chats.List(ctx, params)(ChatsResult, error)
result, err := bot.Chats.List(ctx, kappelas.GetChatsParams{Limit: 20, Offset: 0})
fmt.Println(result.Chats, result.HasMore)
Chats.Iterate(ctx, pageSize, fn)error
err := bot.Chats.Iterate(ctx, 50, func(chat *kappelas.Chat) bool {
    fmt.Println(chat.ChatID, chat.Type)
    return true // return false to stop early
})

Chat fields

chat.ChatID              // int64         — conversation ID
chat.Type                // ChatType      — "private" | "group" | "channel"
chat.Title               // *string       — group/channel name (nil for private)
chat.Participants        // []Participant — members (private only; empty for large groups)
chat.LastMessageAt       // *string       — ISO 8601 timestamp of last message
chat.CreatedAt           // string        — ISO 8601 creation timestamp
chat.IsPublic            // bool          — public group or channel
chat.OnlyAdminsCanWrite  // bool          — only admins can post
chat.Description         // *string       — group/channel description
chat.AvatarURL           // *string       — avatar image URL

Groups & channels

Bots work identically in private chats, groups, and channels — same API, same events. The only requirement is that the bot must be a member of the conversation.

Receiving group messages

When a bot is added to a group or channel, it automatically receives every message posted there via the same OnMessage handler used for DMs.

bot.OnMessage(func(msg *kappelas.Message) {
    // msg.ChatID    — the group's id
    // msg.ChatType  — pointer to "private" | "group" | "channel"
    // msg.SenderID  — UUID of the user who sent the message
    // msg.Text      — message content (nil for media-only)
})

The ChatType field lets you distinguish where a message came from without an extra API call.

Replying in a group

ReplyToID attaches a quote banner to your message. It works identically in private chats, groups, and channels. In groups, always quote the user you're responding to — it makes the context clear to all members.

bot.OnMessage(func(msg *kappelas.Message) {
    ctx := context.Background()
    name := "ami"
    if msg.SenderName != nil {
        name = *msg.SenderName
    }
    bot.Messages.Send(ctx, kappelas.SendMessageParams{
        ChatID:    msg.ChatID,
        Text:      "Reçu, " + name + " 👋",
        ReplyToID: &msg.ID, // quotes the original message
    })
})

Quoting any historical messageReplyToID accepts any MessageID, not just the one that triggered the event:

historyID := int64(456)
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID:    msg.ChatID,
    Text:      "Voici la réponse à ta question précédente :",
    ReplyToID: &historyID,
})

Works on all Send methods* — SendPhoto, SendVideo, SendDocument, SendAudio, and SendCarousel all accept ReplyToID:

replyTo := msg.ID
bot.Messages.SendCarousel(ctx, kappelas.SendCarouselParams{
    ChatID:    msg.ChatID,
    Text:      "Voici nos produits :",
    Carousel:  []kappelas.CarouselCard{{ID: "p1", Title: "Produit A"}},
    ReplyToID: &replyTo, // banner shows above the carousel
})
Getting member IDs

There are three ways to obtain the UserID of members in a group or channel:

1. From incoming messages — the simplest. msg.SenderID is always set on every message event:

bot.OnMessage(func(msg *kappelas.Message) {
    if msg.ChatType != nil && *msg.ChatType == kappelas.ChatTypeGroup {
        fmt.Println(*msg.SenderID)   // UUID of the sender
        fmt.Println(*msg.SenderName) // display name (nil in private chats)
    }
})

2. From the participants listChats.List() returns the full member list for each chat:

result, err := bot.Chats.List(ctx, kappelas.GetChatsParams{Limit: 50})
for _, chat := range result.Chats {
    for _, member := range chat.Participants {
        fmt.Println(member.ID)     // UUID — use as UserID in member calls
        fmt.Println(member.Nom)    // display name
        fmt.Println(member.IsBot)  // true if this participant is a bot
        if member.Role != nil {
            fmt.Println(*member.Role) // "admin" | "member" (nil on private chats)
        }
    }
}

3. From Chats.GetAdministrators() — when you only need admin IDs:

result, err := bot.Chats.GetAdministrators(ctx, kappelas.GetChatAdministratorsParams{ChatID: 42})
for _, admin := range result.Admins {
    fmt.Println(admin.UserID) // UUID
}

Chats.GetMember() lets you check whether a specific user is still in the group and what their current role is — useful after a BanMember or PromoteMember call to confirm the change.

Detecting conversation type

msg.ChatType is available on every incoming message. Use it to adapt bot behaviour per context:

bot.OnMessage(func(msg *kappelas.Message) {
    ctx := context.Background()
    if msg.ChatType == nil {
        return
    }
    switch *msg.ChatType {
    case kappelas.ChatTypePrivate:
        // 1-on-1 chat — show full keyboard, personalise replies
        bot.Messages.Send(ctx, kappelas.SendMessageParams{
            ChatID: msg.ChatID,
            Text:   "De quoi as-tu besoin ?",
            ReplyMarkup: kappelas.ScrollKeyboard{
                ScrollKeyboard: []kappelas.ScrollKeyboardButton{
                    {Text: "📦 Commandes"}, {Text: "❓ Aide"}, {Text: "⚙️ Réglages"},
                },
            },
        })

    case kappelas.ChatTypeGroup:
        // Multi-user — reply with a quote so context is clear
        bot.Messages.Send(ctx, kappelas.SendMessageParams{
            ChatID:    msg.ChatID,
            Text:      "✅ Noté !",
            ReplyToID: &msg.ID,
        })

    case kappelas.ChatTypeChannel:
        // Bot-only posting — no user interaction expected
    }
})
Full group bot example

A bot that works across private chats, groups, and channels:

package main

import (
    "context"
    "fmt"

    "github.com/Arnel7/kappelas-sdk-go"
)

func main() {
    ctx := context.Background()
    bot := kappelas.NewBot("YOUR_BOT_TOKEN")

    bot.OnMessage(func(msg *kappelas.Message) {
        if msg.Text == nil {
            return
        }

        isGroup   := msg.ChatType != nil && *msg.ChatType == kappelas.ChatTypeGroup
        isPrivate := msg.ChatType != nil && *msg.ChatType == kappelas.ChatTypePrivate

        // /status command — works anywhere
        if *msg.Text == "/status" {
            p := kappelas.SendMessageParams{
                ChatID: msg.ChatID,
                Text:   "🟢 Bot en ligne",
            }
            if isGroup {
                p.ReplyToID = &msg.ID // quote in groups
            }
            bot.Messages.Send(ctx, p)
            return
        }

        // /invite command — admin-only, group/channel only
        if *msg.Text == "/invite" && !isPrivate {
            link, err := bot.Chats.CreateInviteLink(ctx, kappelas.CreateChatInviteLinkParams{
                ChatID: msg.ChatID,
            })
            if err != nil {
                bot.Messages.Send(ctx, kappelas.SendMessageParams{
                    ChatID: msg.ChatID,
                    Text:   "❌ J'ai besoin des droits admin pour créer des liens d'invitation.",
                })
                return
            }
            p := kappelas.SendMessageParams{
                ChatID: msg.ChatID,
                Text:   fmt.Sprintf("🔗 Lien d'invitation : %s", link.URL),
            }
            if isGroup {
                p.ReplyToID = &msg.ID
            }
            bot.Messages.Send(ctx, p)
            return
        }

        // Private only — interactive keyboard
        if isPrivate {
            yes, help := "orders", "help"
            bot.Messages.Send(ctx, kappelas.SendMessageParams{
                ChatID: msg.ChatID,
                Text:   "De quoi as-tu besoin ?",
                ReplyMarkup: kappelas.InlineKeyboard{
                    InlineKeyboard: [][]kappelas.InlineKeyboardButton{{
                        {Text: "📦 Commandes", CallbackData: &yes},
                        {Text: "❓ Aide",      CallbackData: &help},
                    }},
                },
            })
        }
    })

    bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) {
        bot.Messages.Send(ctx, kappelas.SendMessageParams{
            ChatID: cb.ChatID,
            Text:   "Tu as choisi : " + cb.CallbackData,
        })
    })

    bot.Start()
    select {}
}
Chats.GetMyGroups(ctx)(*GetMyGroupsResult, error)

Returns every group and channel the bot belongs to, with the bot's role in each.

result, err := bot.Chats.GetMyGroups(ctx)
for _, g := range result.Groups {
    title := "(sans titre)"
    if g.Title != nil {
        title = *g.Title
    }
    fmt.Printf("%d (%s) %q → %s\n", g.ChatID, g.Type, title, g.BotRole)
}

// Filter to groups where the bot is admin
for _, g := range result.Groups {
    if g.BotRole == kappelas.ParticipantRoleAdmin {
        // can create invite links, manage members…
    }
}

BotGroupEntry fields:

Field Type Description
ChatID int64 Conversation ID
Type ChatType "group" or "channel" (never "private")
Title *string Group or channel name
ParticipantCount int Total members (including the bot)
BotRole ParticipantRole "member" or "admin"

Chat member management

All methods below require the bot to be a member of the conversation.
Methods that modify membership (AddMember, BanMember, PromoteMember) additionally require admin rights.

Chats.GetAdministrators(ctx, params)(*GetChatAdministratorsResult, error)
result, err := bot.Chats.GetAdministrators(ctx, kappelas.GetChatAdministratorsParams{
    ChatID: 42,
})
for _, admin := range result.Admins {
    fmt.Println(admin.UserID, admin.Role) // role is always "admin"
}
Chats.GetMember(ctx, params)(*ChatMemberInfo, error)

Returns the role of a specific member. Returns ErrCodeNotFound if the user is not in the conversation.

member, err := bot.Chats.GetMember(ctx, kappelas.GetChatMemberParams{
    ChatID: 42,
    UserID: "user-uuid",
})
fmt.Println(member.Role) // "admin" | "member"
Chats.AddMember(ctx, params)(*AddChatMemberResult, error)
bot.Chats.AddMember(ctx, kappelas.AddChatMemberParams{
    ChatID: 42,
    UserID: "user-uuid",
})
Chats.BanMember(ctx, params)(*BanChatMemberResult, error)

Removes (kicks) a user. To remove the bot itself, use LeaveChat instead.

bot.Chats.BanMember(ctx, kappelas.BanChatMemberParams{
    ChatID: 42,
    UserID: "user-uuid",
})
Chats.PromoteMember(ctx, params)(*PromoteChatMemberResult, error)
// Promote to admin
bot.Chats.PromoteMember(ctx, kappelas.PromoteChatMemberParams{
    ChatID: 42,
    UserID: "user-uuid",
    Role:   kappelas.ParticipantRoleAdmin,
})

// Demote back to member
bot.Chats.PromoteMember(ctx, kappelas.PromoteChatMemberParams{
    ChatID: 42,
    UserID: "user-uuid",
    Role:   kappelas.ParticipantRoleMember,
})
Chats.LeaveChat(ctx, params)(*LeaveChatResult, error)
bot.Chats.LeaveChat(ctx, kappelas.LeaveChatParams{ChatID: 42})

All invite link methods require admin rights.

// Permanent link, unlimited uses
link, err := bot.Chats.CreateInviteLink(ctx, kappelas.CreateChatInviteLinkParams{
    ChatID: 42,
})
fmt.Println(link.URL) // "https://kappelas.com/invite/aBcD123xyz"

// Single-use, expires in 24 h
link, err := bot.Chats.CreateInviteLink(ctx, kappelas.CreateChatInviteLinkParams{
    ChatID:    42,
    MaxUses:   1,
    ExpiresIn: "24h",
})

ExpiresIn values: "1h" · "24h" · "7d" · "30d" · "never" (default)

Shorthand for CreateInviteLink with MaxUses: 1.

link, err := bot.Chats.CreateSingleUseInviteLink(ctx, kappelas.CreateChatInviteLinkParams{
    ChatID: 42,
})
result, err := bot.Chats.GetInviteLinks(ctx, kappelas.GetChatInviteLinksParams{ChatID: 42})
for _, link := range result.InviteLinks {
    max := "∞"
    if link.MaxUses > 0 {
        max = strconv.Itoa(link.MaxUses)
    }
    fmt.Printf("%s — %d/%s uses\n", link.URL, link.UseCount, max)
}
result, err := bot.Chats.RevokeInviteLink(ctx, kappelas.RevokeChatInviteLinkParams{
    ChatID: 42,
    Code:   "aBcD123xyz", // link.Code from CreateInviteLink
})
fmt.Println(result.Revoked) // true

webhooks
Webhooks.Set(ctx, params)(*WebhookSetResult, error)
bot.Webhooks.Set(ctx, kappelas.SetWebhookParams{
    URL: "https://your-server.com/kappela-webhook",
})
Webhooks.GetInfo(ctx)(*WebhookInfo, error)
info, err := bot.Webhooks.GetInfo(ctx)
// → WebhookInfo{Active: true, URL: &"https://…", CreatedAt: &1234567890}
Webhooks.Delete(ctx)(*WebhookDeleteResult, error)
bot.Webhooks.Delete(ctx)
// → WebhookDeleteResult{Active: false}

profile
Profile.Get(ctx)(*BotProfile, error) / (*UserProfile, error)
// Bot
profile, err := bot.Profile.Get(ctx)
// → BotProfile{UserID, Username, IsBot: true, About, Description, AvatarURL}

// User
profile, err := me.Profile.Get(ctx)
// → UserProfile{ID, Username, Nom, IsBot: false, IsPremium, AvatarURL, …}

Keyboards

Three keyboard types can be passed as ReplyMarkup on any Send* call.

Comparison
Inline Reply Scroll
Position Attached to the message Below the input bar Horizontal chips above input
Stays after tap ✅ Yes ❌ Dismissed ✅ Yes
Separate CallbackData ✅ Always ✅ Yes (long form) ✅ Yes (long form)
URL button ✅ Yes ❌ No ❌ No
Layout 2-D grid [][] 2-D grid [][] 1-D list []

Inline keyboard — attached to the message

Buttons stay visible after being tapped. Each button fires a CallbackQuery (CallbackData) or opens a URL (URL).

yes, no := "yes", "no"
url := "https://kappelas.com"
inline := kappelas.InlineKeyboard{
    InlineKeyboard: [][]kappelas.InlineKeyboardButton{
        {
            {Text: "✅ Confirmer", CallbackData: &yes},
            {Text: "❌ Annuler",   CallbackData: &no},
        },
        {
            {Text: "🌐 Site web", URL: &url},
        },
    },
}
Reply keyboard — shown below the input bar

Dismissed after the user taps a button. Buttons trigger a CallbackQuery.

Short form — label and callback value are the same:

reply := kappelas.ReplyKeyboard{
    Keyboard: [][]kappelas.ReplyKeyboardButton{
        {{Text: "📦 Mes commandes"}, {Text: "❓ Aide"}},
        {{Text: "🔙 Retour"}},
    },
}

Long form — separate label and callback value:

reply := kappelas.ReplyKeyboard{
    Keyboard: [][]kappelas.ReplyKeyboardButton{
        {
            {Text: "✅ Confirmer", CallbackData: "confirm_yes"},
            {Text: "❌ Annuler",   CallbackData: "confirm_no"},
        },
        {
            {Text: "↩ Retour", CallbackData: "cancel"},
        },
    },
}

You can mix short and long buttons in the same grid:

reply := kappelas.ReplyKeyboard{
    Keyboard: [][]kappelas.ReplyKeyboardButton{
        {{Text: "✅ Confirmer", CallbackData: "confirm"}, {Text: "❓ Aide"}},
    },
}
Scroll keyboard — horizontal scrollable chips

A single row of chips, always visible above the input bar.

// Short form
scroll := kappelas.ScrollKeyboard{
    ScrollKeyboard: []kappelas.ScrollKeyboardButton{
        {Text: "Petit"}, {Text: "Moyen"}, {Text: "Grand"},
    },
}

// Long form — emoji label, clean callback value
scroll := kappelas.ScrollKeyboard{
    ScrollKeyboard: []kappelas.ScrollKeyboardButton{
        {Text: "📦 Commandes", CallbackData: "menu_orders"},
        {Text: "❓ Aide",      CallbackData: "menu_help"},
        {Text: "⚙️ Réglages",  CallbackData: "menu_settings"},
    },
}
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID:      42,
    Text:        "Choisis une option :",
    ReplyMarkup: inline, // or reply, or scroll
})
Full example — all three in one bot
package main

import (
    "context"
    "github.com/Arnel7/kappelas-sdk-go"
)

func main() {
    ctx := context.Background()
    bot := kappelas.NewBot("YOUR_BOT_TOKEN")

    bot.OnMessage(func(msg *kappelas.Message) {
        if msg.Text == nil || *msg.Text != "/start" {
            return
        }
        // Persistent navigation chips
        orders, help := "menu_orders", "menu_help"
        bot.Messages.Send(ctx, kappelas.SendMessageParams{
            ChatID: msg.ChatID,
            Text:   "Bienvenue ! De quoi as-tu besoin ?",
            ReplyMarkup: kappelas.ScrollKeyboard{
                ScrollKeyboard: []kappelas.ScrollKeyboardButton{
                    {Text: "📦 Commandes", CallbackData: orders},
                    {Text: "❓ Aide",      CallbackData: help},
                },
            },
        })
    })

    bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) {
        switch cb.CallbackData {
        case "menu_orders":
            // Inline confirm/cancel buttons
            confirm, cancel := "order_confirm", "order_cancel"
            bot.Messages.Send(ctx, kappelas.SendMessageParams{
                ChatID: cb.ChatID,
                Text:   "Confirmer ta dernière commande ?",
                ReplyMarkup: kappelas.InlineKeyboard{
                    InlineKeyboard: [][]kappelas.InlineKeyboardButton{{
                        {Text: "✅ Confirmer", CallbackData: &confirm},
                        {Text: "❌ Annuler",   CallbackData: &cancel},
                    }},
                },
            })

        case "menu_help":
            // Reply keyboard for topic selection
            bot.Messages.Send(ctx, kappelas.SendMessageParams{
                ChatID: cb.ChatID,
                Text:   "Quel sujet ?",
                ReplyMarkup: kappelas.ReplyKeyboard{
                    Keyboard: [][]kappelas.ReplyKeyboardButton{
                        {
                            {Text: "💳 Facturation", CallbackData: "help_billing"},
                            {Text: "🚚 Livraison",   CallbackData: "help_delivery"},
                        },
                        {{Text: "↩ Retour au menu", CallbackData: "menu_back"}},
                    },
                },
            })
        }
    })

    bot.Start()
    select {}
}

Text formatting

Kappela renders a WhatsApp/Telegram-style subset of Markdown in every message bubble — bot messages, group messages, and private chat messages. All formatting is applied client-side by the Android app; you only need to send the correct markup in the Text or Caption field.

Inline styles
Syntax Result
**bold** or *bold* Bold
__italic__ or _italic_ Italic
~strikethrough~ Strikethrough
`inline code` Monospace with a tinted background
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   "Commande *confirmée* ✅\nTotal : **24 990 FCFA**\nRef : `ORD-2024-001`",
})
Block code

Triple backticks render as a block code card — only when placed on their own line.

Position Rendu
`code` en cours de phrase Monospace inline avec fond teinté
```code``` sur sa propre ligne Carte pleine largeur + bouton copier
// Inline — reste dans le flux de texte
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   "Ta ref est `ORD-2024-001` — garde-la précieusement.",
})

// Block — doit être sur sa propre ligne pour s'afficher en carte
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   "Ta clé API :\n```\nsk_live_abc123xyz\n```",
})

The code card collapses to a single line with an ellipsis if the content is too long. Tapping anywhere on the card copies the content to the clipboard.

Blockquote / citation

Prefix a line with > to render it as a citation banner (a bar on the left, italic, slightly faded):

bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   "> Question originale ici\n\nVoici ta réponse.",
})

You can combine blockquotes with ReplyToID — use ReplyToID when you want to quote a specific existing message (the app shows a reply banner); use > when you want to render a quote inline within the text itself.

Mentions and commands

@username and /command are auto-detected and rendered as tappable blue links:

// Mention a user by their username
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   "Merci @arnell, ta commande est prête !",
})

// Send a command hint
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   "Tape /help pour voir toutes les commandes disponibles.",
})

Protection rule: @ and / inside URLs are never formatted. @buy_something_bot is treated as a mention, not as buy + _something_bot (italic).

The renderer automatically makes the following clickable without any markup:

Pattern Behaviour
https://… or http://… Opens in the in-app browser
domain.com, domain.io, domain.fr Prefixed with https:// and opened
email@example.com Opens the mail app
+229 01 62 86 15 71, (229) 0162-861571 Opens the dialler
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   "Visitez kappelas.com ou contactez-nous à support@kappelas.com",
})

Supported domain extensions — only the following TLDs are auto-linked: com org net fr io dev co me app tech info biz xyz eu uk de ru tv cc gg ai be ch ca

Country codes like .bj, .sn, .ci are not auto-detected — use a full https:// URL instead: https://kappelas.bj.

Phone format — any sequence of 8+ digits is detected, with spaces, dashes, and parentheses allowed: +229 01 62 86 15 71, +22901628​61571, (229) 0162-861571 all open the dialler.

Combining formats

All inline styles can be combined freely:

lines := []string{
    "🛒 *Récapitulatif de commande*",
    "",
    "> Widget A × 2",
    "",
    "Total : **49 980 FCFA**",
    "Statut : `CONFIRMÉ`",
    "",
    "Des questions ? Contactez support@kappelas.com ou tapez /help",
}
bot.Messages.Send(ctx, kappelas.SendMessageParams{
    ChatID: 42,
    Text:   strings.Join(lines, "\n"),
})

Renders as:

🛒 Récapitulatif de commande   ← bold

┃ Widget A × 2                 ← blockquote (italic, faded)

Total : 49 980 FCFA            ← bold amount
Statut : CONFIRMÉ              ← monospace badge

Des questions ? Contactez support@kappelas.com ou tapez /help
                               ← email and /help are tappable

Error handling

All API errors return a *KappelaError with structured fields:

import "errors"

_, err := bot.Messages.Send(ctx, kappelas.SendMessageParams{ChatID: 999, Text: "Hi"})
if err != nil {
    var e *kappelas.KappelaError
    if errors.As(err, &e) {
        e.Code      // kappelas.ErrCodeForbidden
        e.Status    // 403
        e.Message   // server error message
        e.RequestID // mention this when contacting support
        fmt.Println(e) // formatted block with hints
    }
}
Error codes
Code HTTP Meaning
ErrCodeUnauthorized 401 Token or API key invalid / expired
ErrCodeForbidden 403 Missing permission or role (bot not in chat, not admin…)
ErrCodeNotFound 404 Resource does not exist
ErrCodeMissingField 400 Required parameter missing
ErrCodeInvalidField 400 Parameter has wrong type or format
ErrCodeConflict 409 Resource already exists
ErrCodeMethodNotAllowed 405 Wrong HTTP method
ErrCodeInvalidPath 404 API path does not exist
ErrCodeInternalError 500 Unexpected server error
ErrCodeServiceUnavailable 503 Service temporarily down
ErrCodeUpstreamError 502 Upstream service error

ErrCodeForbidden (not ErrCodeNotFound) is returned when the bot tries to send a message to a chat it has never joined.


File input

Media methods accept a FileInput struct:

type FileInput struct {
    Data        []byte
    Filename    string
    ContentType string
}
// From disk
data, _ := os.ReadFile("photo.jpg")
bot.Messages.SendPhoto(ctx, kappelas.SendMediaParams{
    ChatID: 42,
    File:   kappelas.FileInput{Data: data, Filename: "photo.jpg", ContentType: "image/jpeg"},
})

// From memory
bot.Messages.SendDocument(ctx, kappelas.SendMediaParams{
    ChatID: 42,
    File:   kappelas.FileInput{Data: pdfBytes, Filename: "rapport.pdf", ContentType: "application/pdf"},
})

The Go SDK accepts raw bytes only. To send a file from an HTTPS URL, fetch it first with http.Get and pass the response body as Data.


License

MIT © Arnel LAWSON

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AddChatMemberParams added in v0.1.2

type AddChatMemberParams struct {
	ChatID int64  `json:"chat_id"`
	UserID string `json:"user_id"`
}

AddChatMemberParams holds the parameters for adding a user to a group or channel. The bot must be admin of the conversation.

type AddChatMemberResult added in v0.1.2

type AddChatMemberResult struct {
	Description string `json:"description"`
}

AddChatMemberResult is returned after successfully adding a user.

type BanChatMemberParams added in v0.1.2

type BanChatMemberParams struct {
	ChatID int64  `json:"chat_id"`
	UserID string `json:"user_id"`
}

BanChatMemberParams holds the parameters for removing (kicking) a user. The bot must be admin. To remove itself, use LeaveChatParams instead.

type BanChatMemberResult added in v0.1.2

type BanChatMemberResult struct {
	Description string `json:"description"`
}

BanChatMemberResult is returned after successfully removing a user.

type Bot

type Bot struct {
	// Messages provides methods to send and manage messages.
	Messages *MessagesResource
	// Chats provides methods to list and iterate over chats.
	Chats *ChatsResource
	// Webhooks provides methods to manage webhooks.
	Webhooks *WebhooksResource
	// Profile provides access to the bot's own profile.
	Profile *BotProfileResource
	// contains filtered or unexported fields
}

Bot is the Kappela bot client. Authenticate with a token from BotMother.

Example:

bot := kappelas.NewBot("YOUR_BOT_TOKEN")

bot.OnMessage(func(msg *kappelas.Message) {
    bot.Messages.Send(ctx, kappelas.SendMessageParams{
        ChatID: msg.ChatID,
        Text:   "Echo: " + *msg.Text,
    })
})

bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) {
    bot.Messages.Send(ctx, kappelas.SendMessageParams{
        ChatID: cb.ChatID,
        Text:   "Button clicked: " + cb.CallbackData,
    })
})

bot.Start()
select {} // keep alive

func NewBot

func NewBot(token string, opts ...BotOption) *Bot

NewBot creates a Bot authenticated with the given token.

func (*Bot) Connected

func (b *Bot) Connected() bool

Connected reports whether the WebSocket is currently open.

func (*Bot) HandleWebhook

func (b *Bot) HandleWebhook(body []byte)

HandleWebhook processes a webhook payload sent by Kappela to your server. Call this inside your HTTP handler and respond 200 immediately. The same OnMessage and OnCallbackQuery handlers fire for both WS and webhook events.

Example (net/http):

http.HandleFunc("/kappela-webhook", func(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    bot.HandleWebhook(body)
    w.WriteHeader(http.StatusOK)
})

func (*Bot) OnCallbackQuery

func (b *Bot) OnCallbackQuery(h func(*CallbackQuery))

OnCallbackQuery registers a handler called when a user clicks an inline button.

func (*Bot) OnConnected

func (b *Bot) OnConnected(h func())

OnConnected registers a handler called when the WebSocket connects (or reconnects).

func (*Bot) OnDisconnected

func (b *Bot) OnDisconnected(h func(code int, reason string))

OnDisconnected registers a handler called when the WebSocket disconnects.

func (*Bot) OnError

func (b *Bot) OnError(h func(error))

OnError registers a handler called on WebSocket or connection errors.

func (*Bot) OnMessage

func (b *Bot) OnMessage(h func(*Message))

OnMessage registers a handler called for every incoming message.

func (*Bot) Reply added in v0.1.2

func (b *Bot) Reply(ctx context.Context, event any, text string, opts ...SendMessageParams) (*SendResult, error)

Reply sends a text reply to a Message or CallbackQuery event.

  • When called with a *Message — sets ChatID and ReplyToID automatically (shows a quote banner).
  • When called with a *CallbackQuery — sets ChatID only (callback queries have no message ID).

Pass an optional SendMessageParams to attach a keyboard or set other options. ChatID, ReplyToID, and Text in opts are overwritten automatically.

Example:

bot.OnMessage(func(msg *kappelas.Message) {
    ctx := context.Background()
    bot.Reply(ctx, msg, "Got it! 👋")

    // With an inline keyboard
    bot.Reply(ctx, msg, "Pick one:", kappelas.SendMessageParams{
        ReplyMarkup: kappelas.InlineKeyboard{
            InlineKeyboard: [][]kappelas.InlineKeyboardButton{{
                {Text: "✅ Oui", CallbackData: ptr("yes")},
                {Text: "❌ Non", CallbackData: ptr("no")},
            }},
        },
    })
})

bot.OnCallbackQuery(func(cb *kappelas.CallbackQuery) {
    ctx := context.Background()
    bot.Reply(ctx, cb, "Tu as cliqué : "+cb.CallbackData)
})

func (*Bot) Start

func (b *Bot) Start()

Start connects via WebSocket and begins receiving events in the background. Your OnMessage and OnCallbackQuery handlers will be called for each event.

func (*Bot) Stop

func (b *Bot) Stop()

Stop closes the WebSocket connection.

type BotGroupEntry added in v0.1.2

type BotGroupEntry struct {
	// ChatID is the conversation ID — use this as ChatID in all API calls.
	ChatID int64 `json:"chat_id"`
	// Type is "group" or "channel". Never "private".
	Type ChatType `json:"type"`
	// Title is the group or channel name.
	Title *string `json:"title"`
	// ParticipantCount is the total number of members (including the bot).
	ParticipantCount int `json:"participant_count"`
	// BotRole is the bot's role in this conversation.
	BotRole ParticipantRole `json:"bot_role"`
}

BotGroupEntry is a group or channel the bot belongs to, enriched with its role.

type BotOption

type BotOption func(*botConfig)

BotOption configures a Bot.

func WithBaseURL

func WithBaseURL(u string) BotOption

WithBaseURL overrides the API base URL (default: https://api.kappelas.com).

func WithMaxRetries

func WithMaxRetries(n int) BotOption

WithMaxRetries sets the maximum number of HTTP retry attempts (default: 2).

func WithTimeout

func WithTimeout(d time.Duration) BotOption

WithTimeout sets the HTTP request timeout (default: 30s).

func WithWSMaxRetries

func WithWSMaxRetries(n int) BotOption

WithWSMaxRetries sets the maximum WebSocket reconnect attempts (default: 12).

type BotProfile

type BotProfile struct {
	UserID      string  `json:"user_id"`
	Username    string  `json:"username"`
	IsBot       bool    `json:"is_bot"`
	About       string  `json:"about"`
	Description string  `json:"description"`
	AvatarURL   *string `json:"avatar_url"`
}

BotProfile is the profile returned for a bot account.

type BotProfileResource

type BotProfileResource struct {
	// contains filtered or unexported fields
}

BotProfileResource provides access to the bot's own profile.

func (*BotProfileResource) Get

Get returns the bot's own profile.

type CallbackQuery

type CallbackQuery struct {
	ChatID   int64  `json:"chat_id"`
	SenderID string `json:"sender_id"`
	// SenderNom is the display name of the user who clicked (e.g. "Arnel LAWSON").
	// Note: Message uses SenderName — do not confuse the two.
	SenderNom      *string `json:"sender_nom"`
	SenderUsername *string `json:"sender_username"`
	CallbackData   string  `json:"callback_data"`
	SentAt         int64   `json:"sent_at"`
}

CallbackQuery is fired when a user clicks an inline button.

type CarouselCard

type CarouselCard struct {
	ID         string  `json:"id"`
	Title      string  `json:"title"`
	Subtitle   *string `json:"subtitle,omitempty"`
	ImageURL   *string `json:"image_url,omitempty"`
	ButtonText *string `json:"button_text,omitempty"`
}

CarouselCard is a single card inside a carousel message.

type Chat

type Chat struct {
	ChatID             int64         `json:"chat_id"`
	ID                 int64         `json:"id"`
	Type               ChatType      `json:"type"`
	Title              *string       `json:"title"`
	Participants       []Participant `json:"participants"`
	LastMessageAt      *string       `json:"last_message_at"`
	CreatedAt          string        `json:"created_at"`
	CreatedBy          string        `json:"created_by"`
	IsPinned           bool          `json:"is_pinned"`
	IsPremium          bool          `json:"is_premium"`
	IsPublic           bool          `json:"is_public"`
	OnlyAdminsCanWrite bool          `json:"only_admins_can_write"`
	Labels             []string      `json:"labels"`
	Description        *string       `json:"description"`
	AvatarURL          *string       `json:"avatar_url"`
}

Chat represents a Kappela conversation.

type ChatInviteLink struct {
	// Code is the short identifier used in the URL (e.g. "aBcD123xyz").
	Code string `json:"code"`
	// URL is the full invite URL (e.g. "https://kappelas.com/invite/aBcD123xyz").
	URL string `json:"url"`
	// MaxUses is the maximum number of allowed uses (0 = unlimited).
	MaxUses int `json:"max_uses"`
	// UseCount is the current number of uses.
	UseCount int `json:"use_count"`
	// ExpiresAt is the expiry as Unix timestamp (seconds), or nil if permanent.
	ExpiresAt *int64 `json:"expires_at"`
	// CreatedAt is the creation time as Unix timestamp (seconds).
	CreatedAt int64 `json:"created_at"`
}

ChatInviteLink describes an active invite link for a group or channel.

type ChatMemberInfo added in v0.1.2

type ChatMemberInfo struct {
	UserID string          `json:"user_id"`
	Role   ParticipantRole `json:"role"`
}

ChatMemberInfo is a minimal member record returned by GetMember and GetAdministrators.

type ChatType

type ChatType string

ChatType is the type of a conversation.

const (
	ChatTypePrivate ChatType = "private"
	ChatTypeGroup   ChatType = "group"
	ChatTypeChannel ChatType = "channel"
)

type ChatsResource

type ChatsResource struct {
	// contains filtered or unexported fields
}

ChatsResource provides methods to access and manage chats.

func (*ChatsResource) AddMember added in v0.1.2

AddMember adds a user to a group or channel. The bot must be admin of the conversation.

Example:

result, err := bot.Chats.AddMember(ctx, kappelas.AddChatMemberParams{
    ChatID: 42, UserID: "user-uuid",
})

func (*ChatsResource) BanMember added in v0.1.2

BanMember removes (kicks) a user from a group or channel. The bot must be admin. To remove itself, use LeaveChat instead.

Example:

result, err := bot.Chats.BanMember(ctx, kappelas.BanChatMemberParams{
    ChatID: 42, UserID: "user-uuid",
})
func (r *ChatsResource) CreateInviteLink(ctx context.Context, params CreateChatInviteLinkParams) (*ChatInviteLink, error)

CreateInviteLink creates an invite link for a group or channel. The bot must be admin of the conversation.

Example:

// Permanent link, unlimited uses
link, err := bot.Chats.CreateInviteLink(ctx, kappelas.CreateChatInviteLinkParams{
    ChatID: 42,
})
fmt.Println(link.URL) // "https://kappelas.com/invite/aBcD123xyz"

// Single-use, expires in 24 h
link, err := bot.Chats.CreateInviteLink(ctx, kappelas.CreateChatInviteLinkParams{
    ChatID: 42, MaxUses: 1, ExpiresIn: "24h",
})
func (r *ChatsResource) CreateSingleUseInviteLink(ctx context.Context, params CreateChatInviteLinkParams) (*ChatInviteLink, error)

CreateSingleUseInviteLink is a shorthand to create a single-use invite link. Equivalent to CreateInviteLink with MaxUses: 1. The bot must be admin.

Example:

link, err := bot.Chats.CreateSingleUseInviteLink(ctx, kappelas.CreateChatInviteLinkParams{
    ChatID: 42,
})

func (*ChatsResource) GetAdministrators added in v0.1.2

GetAdministrators returns all admins of a group or channel. The bot must be a member of the conversation.

Example:

result, err := bot.Chats.GetAdministrators(ctx, kappelas.GetChatAdministratorsParams{ChatID: 42})
for _, admin := range result.Admins {
    fmt.Println(admin.UserID, admin.Role)
}

GetInviteLinks returns all active invite links for a group or channel. The bot must be admin.

Example:

result, err := bot.Chats.GetInviteLinks(ctx, kappelas.GetChatInviteLinksParams{ChatID: 42})
for _, link := range result.InviteLinks {
    fmt.Printf("%s — %d/%d uses\n", link.URL, link.UseCount, link.MaxUses)
}

func (*ChatsResource) GetMember added in v0.1.2

func (r *ChatsResource) GetMember(ctx context.Context, params GetChatMemberParams) (*ChatMemberInfo, error)

GetMember returns info for a specific member (UserID + Role). The bot must be a member of the conversation. Returns ErrCodeNotFound if the user is not in the conversation.

Example:

member, err := bot.Chats.GetMember(ctx, kappelas.GetChatMemberParams{
    ChatID: 42, UserID: "user-uuid",
})
fmt.Println(member.Role) // "admin" | "member"

func (*ChatsResource) GetMyGroups added in v0.1.2

func (r *ChatsResource) GetMyGroups(ctx context.Context) (*GetMyGroupsResult, error)

GetMyGroups returns every group and channel the bot is a member of, together with the bot's own role in each.

Useful to discover which groups the bot can manage (e.g. create invite links).

Example:

result, err := bot.Chats.GetMyGroups(ctx)
for _, g := range result.Groups {
    fmt.Printf("%d (%s) %q → %s\n", g.ChatID, g.Type, g.Title, g.BotRole)
    if g.BotRole == kappelas.ParticipantRoleAdmin {
        // bot can create invite links, manage members…
    }
}

func (*ChatsResource) Iterate

func (r *ChatsResource) Iterate(ctx context.Context, pageSize int, fn func(*Chat) bool) error

Iterate calls fn for every chat, handling pagination automatically. Return false from fn to stop iteration early.

Example:

err := bot.Chats.Iterate(ctx, 50, func(chat *kappelas.Chat) bool {
    fmt.Println(chat.ChatID, chat.Type)
    return true // continue
})

func (*ChatsResource) LeaveChat added in v0.1.2

func (r *ChatsResource) LeaveChat(ctx context.Context, params LeaveChatParams) (*LeaveChatResult, error)

LeaveChat makes the bot leave a group or channel.

Example:

result, err := bot.Chats.LeaveChat(ctx, kappelas.LeaveChatParams{ChatID: 42})

func (*ChatsResource) List

func (r *ChatsResource) List(ctx context.Context, params GetChatsParams) (ChatsResult, error)

List returns a paginated list of chats accessible to this bot or user.

func (*ChatsResource) PromoteMember added in v0.1.2

PromoteMember promotes or demotes a member. The bot must be admin.

  • Role: ParticipantRoleAdmin — grants admin rights
  • Role: ParticipantRoleMember — revokes admin rights

Example:

// Promote to admin
bot.Chats.PromoteMember(ctx, kappelas.PromoteChatMemberParams{
    ChatID: 42,
    UserID: "user-uuid",
    Role:   kappelas.ParticipantRoleAdmin,
})

RevokeInviteLink revokes an active invite link so it can no longer be used. The bot must be admin.

Example:

result, err := bot.Chats.RevokeInviteLink(ctx, kappelas.RevokeChatInviteLinkParams{
    ChatID: 42, Code: "aBcD123xyz",
})

type ChatsResult

type ChatsResult struct {
	Chats   []Chat `json:"chats"`
	HasMore bool   `json:"has_more"`
}

ChatsResult is the paginated response from the list chats endpoint.

type CreateChatInviteLinkParams added in v0.1.2

type CreateChatInviteLinkParams struct {
	ChatID int64 `json:"chat_id"`
	// MaxUses is 0 for unlimited, or a positive number to cap uses.
	MaxUses int `json:"max_uses,omitempty"`
	// ExpiresIn controls expiry: "1h", "24h", "7d", "30d", or "never" (default).
	ExpiresIn string `json:"expires_in,omitempty"`
}

CreateChatInviteLinkParams holds the parameters for creating an invite link. The bot must be admin of the conversation.

type DeleteMessageParams

type DeleteMessageParams struct {
	ChatID    int64 `json:"chat_id"`
	MessageID int64 `json:"message_id"`
}

DeleteMessageParams holds the parameters for deleting a message.

type DeleteResult

type DeleteResult struct {
	Deleted bool `json:"deleted"`
}

DeleteResult is returned by the deleteMessage endpoint.

type EditMessageParams

type EditMessageParams struct {
	ChatID       int64           `json:"chat_id"`
	MessageID    int64           `json:"message_id"`
	NewText      string          `json:"new_text,omitempty"`
	NewExtraData json.RawMessage `json:"new_extra_data,omitempty"`
}

EditMessageParams holds the parameters for editing a message.

type EditMessageResult

type EditMessageResult struct {
	Edited    bool  `json:"edited"`
	MessageID int64 `json:"message_id"`
}

EditMessageResult is returned after editing a message.

type ErrorCode

type ErrorCode string

ErrorCode is the machine-readable error code returned by the Kappela API.

const (
	ErrCodeUnauthorized       ErrorCode = "UNAUTHORIZED"
	ErrCodeForbidden          ErrorCode = "FORBIDDEN"
	ErrCodeNotFound           ErrorCode = "NOT_FOUND"
	ErrCodeInvalidField       ErrorCode = "INVALID_FIELD"
	ErrCodeMissingField       ErrorCode = "MISSING_FIELD"
	ErrCodeInternalError      ErrorCode = "INTERNAL_ERROR"
	ErrCodeServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE"
	ErrCodeConflict           ErrorCode = "CONFLICT"
	ErrCodeMethodNotAllowed   ErrorCode = "METHOD_NOT_ALLOWED"
	ErrCodeInvalidPath        ErrorCode = "INVALID_PATH"
	ErrCodeUpstreamError      ErrorCode = "UPSTREAM_ERROR"
)

type FileInput

type FileInput struct {
	Data        []byte
	Filename    string
	ContentType string
}

FileInput holds a file to be uploaded.

type GetChatAdministratorsParams added in v0.1.2

type GetChatAdministratorsParams struct {
	ChatID int64 `json:"chat_id"`
}

GetChatAdministratorsParams holds the parameters for fetching chat admins.

type GetChatAdministratorsResult added in v0.1.2

type GetChatAdministratorsResult struct {
	Admins []ChatMemberInfo `json:"admins"`
}

GetChatAdministratorsResult contains all admins of a group or channel.

type GetChatInviteLinksParams added in v0.1.2

type GetChatInviteLinksParams struct {
	ChatID int64 `json:"chat_id"`
}

GetChatInviteLinksParams holds the parameters for listing invite links.

type GetChatInviteLinksResult added in v0.1.2

type GetChatInviteLinksResult struct {
	InviteLinks []ChatInviteLink `json:"invite_links"`
}

GetChatInviteLinksResult contains all active invite links for a group or channel.

type GetChatMemberParams added in v0.1.2

type GetChatMemberParams struct {
	ChatID int64  `json:"chat_id"`
	UserID string `json:"user_id"`
}

GetChatMemberParams holds the parameters for looking up a single member.

type GetChatsParams

type GetChatsParams struct {
	Limit  int
	Offset int
}

GetChatsParams holds the parameters for listing chats.

type GetMyGroupsResult added in v0.1.2

type GetMyGroupsResult struct {
	Groups []BotGroupEntry `json:"groups"`
}

GetMyGroupsResult holds the list of groups and channels the bot belongs to.

type InlineKeyboard

type InlineKeyboard struct {
	InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"`
}

InlineKeyboard renders buttons attached to a message.

type InlineKeyboardButton

type InlineKeyboardButton struct {
	Text         string  `json:"text"`
	CallbackData *string `json:"callback_data,omitempty"`
	URL          *string `json:"url,omitempty"`
}

InlineKeyboardButton is a button inside an inline keyboard.

type KappelaError

type KappelaError struct {
	// Message is the human-readable error description from the API.
	Message string
	// Code is the machine-readable error code.
	Code ErrorCode
	// Status is the HTTP status code.
	Status int
	// RequestID can be quoted when contacting support.
	RequestID string
}

KappelaError is returned when the Kappela API responds with an error.

func (*KappelaError) Error

func (e *KappelaError) Error() string

type LeaveChatParams added in v0.1.2

type LeaveChatParams struct {
	ChatID int64 `json:"chat_id"`
}

LeaveChatParams holds the parameters for the bot to leave a group or channel.

type LeaveChatResult added in v0.1.2

type LeaveChatResult struct {
	Description string `json:"description"`
}

LeaveChatResult is returned after the bot leaves.

type Message

type Message struct {
	ID     int64 `json:"id"`
	ChatID int64 `json:"chat_id"`
	// ChatType is the type of conversation ("private", "group", "channel").
	// Always present on WS and webhook events; may be absent on history API results.
	ChatType        *ChatType       `json:"chat_type,omitempty"`
	SenderID        *string         `json:"sender_id"`
	Type            MessageType     `json:"type"`
	Text            *string         `json:"text"`
	MediaID         *string         `json:"media_id"`
	ExtraData       json.RawMessage `json:"extra_data"`
	Status          MessageStatus   `json:"status"`
	EditedAt        *int64          `json:"edited_at"`
	DeletedAt       *int64          `json:"deleted_at"`
	CreatedAt       int64           `json:"created_at"`
	ReplyToID       *int64          `json:"reply_to_id"`
	ReplyToSnapshot *ReplySnapshot  `json:"reply_to_snapshot"`
	Mentions        []string        `json:"mentions"`
	ForwardedFrom   json.RawMessage `json:"forwarded_from"`
	ExpiresAt       *int64          `json:"expires_at"`
	// SenderName is the display name of the sender.
	// Only present on messages in groups and channels — absent in private chats.
	// Note: CallbackQuery has SenderNom (not SenderName) — do not confuse the two.
	SenderName      *string `json:"sender_name,omitempty"`
	SenderAvatarURL *string `json:"sender_avatar_url,omitempty"`
	ClientMsgID     string  `json:"client_msg_id,omitempty"`
	Width           *int    `json:"width,omitempty"`
	Height          *int    `json:"height,omitempty"`
}

Message represents a Kappela chat message.

type MessageStatus

type MessageStatus string

MessageStatus is the delivery status of a message.

const (
	MessageStatusSent      MessageStatus = "sent"
	MessageStatusDelivered MessageStatus = "delivered"
	MessageStatusRead      MessageStatus = "read"
)

type MessageType

type MessageType string

MessageType is the content type of a message.

const (
	MessageTypeText     MessageType = "text"
	MessageTypeImage    MessageType = "image"
	MessageTypeVideo    MessageType = "video"
	MessageTypeAudio    MessageType = "audio"
	MessageTypeDocument MessageType = "document"
	MessageTypeSystem   MessageType = "system"
	MessageTypePoll     MessageType = "poll"
	MessageTypeSticker  MessageType = "sticker"
	MessageTypeLocation MessageType = "location"
	MessageTypeContact  MessageType = "contact"
)

type MessagesResource

type MessagesResource struct {
	// contains filtered or unexported fields
}

MessagesResource provides methods to send and manage messages.

func (*MessagesResource) Delete

Delete deletes a message sent by this bot or user.

func (*MessagesResource) Edit

Edit edits the text or inline keyboard of a message sent by this bot or user.

func (*MessagesResource) Send

Send sends a text message, with optional inline buttons or keyboard.

func (*MessagesResource) SendAudio

func (r *MessagesResource) SendAudio(ctx context.Context, params SendMediaParams) (*SendMediaResult, error)

SendAudio sends an audio file.

func (*MessagesResource) SendCarousel

SendCarousel sends a product or card carousel.

func (*MessagesResource) SendDocument

func (r *MessagesResource) SendDocument(ctx context.Context, params SendMediaParams) (*SendMediaResult, error)

SendDocument sends a document or generic file.

func (*MessagesResource) SendPhoto

func (r *MessagesResource) SendPhoto(ctx context.Context, params SendMediaParams) (*SendMediaResult, error)

SendPhoto sends a photo (image file).

func (*MessagesResource) SendTyping

func (r *MessagesResource) SendTyping(ctx context.Context, params SendTypingParams) (*TypingResult, error)

SendTyping shows or hides the typing indicator in a chat. IsTyping defaults to true when not set.

func (*MessagesResource) SendVideo

func (r *MessagesResource) SendVideo(ctx context.Context, params SendMediaParams) (*SendMediaResult, error)

SendVideo sends a video file.

type Participant

type Participant struct {
	ID        string  `json:"id"`
	Nom       string  `json:"nom"`
	IsBot     bool    `json:"is_bot"`
	IsPremium bool    `json:"is_premium"`
	AvatarURL *string `json:"avatar_url"`
	// Role is the member's role in the conversation.
	// Present on groups and channels; absent on private chats.
	Role *ParticipantRole `json:"role,omitempty"`
}

Participant is a member of a chat.

type ParticipantRole added in v0.1.2

type ParticipantRole string

ParticipantRole is the role of a member in a group or channel.

const (
	ParticipantRoleMember ParticipantRole = "member"
	ParticipantRoleAdmin  ParticipantRole = "admin"
)

type PrivacySetting

type PrivacySetting string

PrivacySetting is a user privacy configuration value.

const (
	PrivacyEveryone PrivacySetting = "everyone"
	PrivacyContacts PrivacySetting = "contacts"
	PrivacyNobody   PrivacySetting = "nobody"
)

type PromoteChatMemberParams added in v0.1.2

type PromoteChatMemberParams struct {
	ChatID int64  `json:"chat_id"`
	UserID string `json:"user_id"`
	// Role: ParticipantRoleAdmin promotes, ParticipantRoleMember demotes.
	Role ParticipantRole `json:"role"`
}

PromoteChatMemberParams holds the parameters for changing a member's role. The bot must be admin.

type PromoteChatMemberResult added in v0.1.2

type PromoteChatMemberResult struct {
	UserID string          `json:"user_id"`
	Role   ParticipantRole `json:"role"`
}

PromoteChatMemberResult is returned after a role change.

type ReplyKeyboard

type ReplyKeyboard struct {
	Keyboard [][]ReplyKeyboardButton `json:"keyboard"`
}

ReplyKeyboard renders a custom keyboard shown below the message input field.

type ReplyKeyboardButton added in v0.1.2

type ReplyKeyboardButton struct {
	// Text is the label shown on the button.
	Text string
	// CallbackData is the value sent to the webhook when the button is pressed.
	// Defaults to Text when empty.
	CallbackData string
}

ReplyKeyboardButton is a single button in a reply or scroll keyboard.

Short form — label and callback value are identical:

ReplyKeyboardButton{Text: "Option A"}

Long form — different label and callback value:

ReplyKeyboardButton{Text: "✅ Confirmer", CallbackData: "confirm_yes"}

func (ReplyKeyboardButton) MarshalJSON added in v0.1.2

func (b ReplyKeyboardButton) MarshalJSON() ([]byte, error)

MarshalJSON serialises as a plain string when CallbackData is empty or equals Text, and as {"text":…,"callback_data":…} when they differ.

type ReplySnapshot

type ReplySnapshot struct {
	MessageID int64       `json:"message_id"`
	SenderID  *string     `json:"sender_id"`
	Type      MessageType `json:"type"`
	Text      *string     `json:"text"`
	MediaID   *string     `json:"media_id"`
}

ReplySnapshot is a lightweight snapshot of the message being replied to.

type RevokeChatInviteLinkParams added in v0.1.2

type RevokeChatInviteLinkParams struct {
	ChatID int64 `json:"chat_id"`
	// Code is the code field of the link to revoke.
	Code string `json:"code"`
}

RevokeChatInviteLinkParams holds the parameters for revoking an invite link.

type RevokeChatInviteLinkResult added in v0.1.2

type RevokeChatInviteLinkResult struct {
	Revoked bool   `json:"revoked"`
	Code    string `json:"code"`
}

RevokeChatInviteLinkResult is returned after revoking a link.

type ScrollKeyboard

type ScrollKeyboard struct {
	ScrollKeyboard []ScrollKeyboardButton `json:"scroll_keyboard"`
}

ScrollKeyboard renders a horizontally scrollable chip bar below the message input.

type ScrollKeyboardButton added in v0.1.2

type ScrollKeyboardButton = ReplyKeyboardButton

ScrollKeyboardButton is a button in a scroll (horizontal chips) keyboard. Same short/long form as ReplyKeyboardButton.

type SendCarouselParams

type SendCarouselParams struct {
	ChatID   int64          `json:"chat_id"`
	Text     string         `json:"text,omitempty"`
	Carousel []CarouselCard `json:"carousel"`
	// QuickReplyButtons are shown as chips below the carousel.
	// Accepts short form {Text: "label"} or long form {Text: "label", CallbackData: "value"}.
	QuickReplyButtons []ScrollKeyboardButton `json:"quick_reply_buttons,omitempty"`
	ReplyToID         *int64                 `json:"reply_to_id,omitempty"`
	DeletePrevious    bool                   `json:"delete_previous,omitempty"`
}

SendCarouselParams holds the parameters for sending a product carousel.

type SendCarouselResult

type SendCarouselResult struct {
	MessageID int64  `json:"message_id"`
	CreatedAt int64  `json:"created_at"`
	Type      string `json:"type"`
}

SendCarouselResult is returned after sending a carousel.

type SendMediaParams

type SendMediaParams struct {
	ChatID         int64
	File           FileInput
	Caption        string
	ReplyToID      *int64
	DeletePrevious bool
	// ReplyMarkup accepts InlineKeyboard, ReplyKeyboard, or ScrollKeyboard.
	ReplyMarkup any
}

SendMediaParams holds the parameters for sending a photo, video, document, or audio.

type SendMediaResult

type SendMediaResult struct {
	MessageID int64  `json:"message_id"`
	CreatedAt int64  `json:"created_at"`
	MediaID   string `json:"media_id"`
}

SendMediaResult is returned after sending a media message.

type SendMessageParams

type SendMessageParams struct {
	ChatID int64  `json:"chat_id"`
	Text   string `json:"text"`
	// ReplyMarkup accepts InlineKeyboard, ReplyKeyboard, or ScrollKeyboard.
	ReplyMarkup    any    `json:"reply_markup,omitempty"`
	ReplyToID      *int64 `json:"reply_to_id,omitempty"`
	DeletePrevious bool   `json:"delete_previous,omitempty"`
}

SendMessageParams holds the parameters for sending a text message.

type SendResult

type SendResult struct {
	MessageID int64 `json:"message_id"`
	CreatedAt int64 `json:"created_at"`
}

SendResult is returned after sending a text message.

type SendTypingParams

type SendTypingParams struct {
	ChatID   int64 `json:"chat_id"`
	IsTyping *bool `json:"is_typing,omitempty"`
}

SendTypingParams holds the parameters for the typing indicator. IsTyping defaults to true when nil (show indicator). Set to false to hide it.

type SetWebhookParams

type SetWebhookParams struct {
	URL    string  `json:"url"`
	Secret *string `json:"secret,omitempty"`
}

SetWebhookParams holds the parameters for registering a webhook.

type TypingResult

type TypingResult struct {
	Typing bool `json:"typing"`
}

TypingResult is returned by the sendTyping endpoint.

type User

type User struct {
	// Messages provides methods to send and manage messages.
	Messages *MessagesResource
	// Chats provides methods to list and iterate over chats.
	Chats *ChatsResource
	// Webhooks provides methods to manage webhooks.
	Webhooks *WebhooksResource
	// Profile provides access to your own profile.
	Profile *UserProfileResource
	// contains filtered or unexported fields
}

User is the Kappela personal automation client. Authenticate with a personal API key (sk_...) to send messages and receive events as yourself.

Example:

me := kappelas.NewUser("sk_...")

me.OnMessage(func(msg *kappelas.Message) {
    fmt.Println("New message:", msg.Text)
})

me.Start()
select {}

func NewUser

func NewUser(apiKey string, opts ...UserOption) *User

NewUser creates a User authenticated with the given personal API key.

func (*User) Connected

func (u *User) Connected() bool

Connected reports whether the WebSocket is currently open.

func (*User) HandleWebhook

func (u *User) HandleWebhook(body []byte)

HandleWebhook processes a webhook payload sent by Kappela to your server.

Example (net/http):

http.HandleFunc("/kappela-webhook", func(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    me.HandleWebhook(body)
    w.WriteHeader(http.StatusOK)
})

func (*User) OnCallbackQuery

func (u *User) OnCallbackQuery(h func(*CallbackQuery))

OnCallbackQuery registers a handler called when a user clicks an inline button.

func (*User) OnConnected

func (u *User) OnConnected(h func())

OnConnected registers a handler called when the WebSocket connects (or reconnects).

func (*User) OnDisconnected

func (u *User) OnDisconnected(h func(code int, reason string))

OnDisconnected registers a handler called when the WebSocket disconnects.

func (*User) OnError

func (u *User) OnError(h func(error))

OnError registers a handler called on WebSocket or connection errors.

func (*User) OnMessage

func (u *User) OnMessage(h func(*Message))

OnMessage registers a handler called for every incoming message.

func (*User) Start

func (u *User) Start()

Start connects via WebSocket and begins receiving events in the background.

func (*User) Stop

func (u *User) Stop()

Stop closes the WebSocket connection.

type UserOption

type UserOption func(*userConfig)

UserOption configures a User.

func WithUserBaseURL

func WithUserBaseURL(u string) UserOption

WithUserBaseURL overrides the API base URL for a User client.

func WithUserMaxRetries

func WithUserMaxRetries(n int) UserOption

WithUserMaxRetries sets the maximum number of HTTP retry attempts for a User client.

func WithUserTimeout

func WithUserTimeout(d time.Duration) UserOption

WithUserTimeout sets the HTTP request timeout for a User client.

func WithUserWSMaxRetries

func WithUserWSMaxRetries(n int) UserOption

WithUserWSMaxRetries sets the maximum WebSocket reconnect attempts for a User client.

type UserProfile

type UserProfile struct {
	ID            string         `json:"id"`
	Username      string         `json:"username"`
	Nom           string         `json:"nom"`
	IsBot         bool           `json:"is_bot"`
	IsPremium     bool           `json:"is_premium"`
	AvatarURL     *string        `json:"avatar_url"`
	AllowGroupAdd PrivacySetting `json:"allow_group_add"`
	AllowCalls    PrivacySetting `json:"allow_calls"`
}

UserProfile is the profile returned for a personal account.

type UserProfileResource

type UserProfileResource struct {
	// contains filtered or unexported fields
}

UserProfileResource provides access to the user's own profile.

func (*UserProfileResource) Get

Get returns your own profile.

type WebhookDeleteResult

type WebhookDeleteResult struct {
	Active bool `json:"active"`
}

WebhookDeleteResult is returned after removing a webhook.

type WebhookInfo

type WebhookInfo struct {
	Active    bool    `json:"active"`
	URL       *string `json:"url"`
	CreatedAt *int64  `json:"created_at"`
}

WebhookInfo describes the current webhook configuration.

type WebhookSetResult

type WebhookSetResult struct {
	URL    string `json:"url"`
	Active bool   `json:"active"`
}

WebhookSetResult is returned after registering a webhook.

type WebhooksResource

type WebhooksResource struct {
	// contains filtered or unexported fields
}

WebhooksResource provides methods to manage webhooks.

func (*WebhooksResource) Delete

Delete removes the webhook. Events will no longer be delivered via HTTP POST.

func (*WebhooksResource) GetInfo

func (r *WebhooksResource) GetInfo(ctx context.Context) (*WebhookInfo, error)

GetInfo returns the current webhook status and URL.

func (*WebhooksResource) Set

Set registers a webhook URL. Use this for production deployments.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL