5 Commits
v0.3.7 ... dev

Author SHA1 Message Date
7a6f135487 plugin middlewares; v0.3.10 2026-02-04 17:27:36 +03:00
2e9e82d43b slices, queue and map now independent module 2026-02-04 12:49:12 +03:00
55d4065259 work on HashMap and slices 2026-02-04 11:59:25 +03:00
b89f27574f work on HashMap and slices 2026-02-04 11:52:25 +03:00
689eb8a5e2 slices additions; v0.3.8 2026-02-04 11:39:08 +03:00
11 changed files with 134 additions and 155 deletions

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# Laniakea
A lightweight, easy to use and performance Telegram API wrapper for bot development.

15
bot.go
View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"time" "time"
"git.nix13.pw/scuroneko/extypes"
"git.nix13.pw/scuroneko/slog" "git.nix13.pw/scuroneko/slog"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"github.com/vinovest/sqlx" "github.com/vinovest/sqlx"
@@ -39,7 +40,7 @@ type Bot struct {
updateOffset int updateOffset int
updateTypes []string updateTypes []string
updateQueue *Queue[*Update] updateQueue *extypes.Queue[*Update]
} }
type BotSettings struct { type BotSettings struct {
@@ -73,7 +74,7 @@ func LoadPrefixesFromEnv() []string {
return strings.Split(prefixesS, ";") return strings.Split(prefixesS, ";")
} }
func NewBot(settings *BotSettings) *Bot { func NewBot(settings *BotSettings) *Bot {
updateQueue := CreateQueue[*Update](256) updateQueue := extypes.CreateQueue[*Update](256)
bot := &Bot{ bot := &Bot{
updateOffset: 0, plugins: make([]Plugin, 0), debug: settings.Debug, errorTemplate: "%s", updateOffset: 0, plugins: make([]Plugin, 0), debug: settings.Debug, errorTemplate: "%s",
prefixes: settings.Prefixes, updateTypes: make([]string, 0), runners: make([]Runner, 0), prefixes: settings.Prefixes, updateTypes: make([]string, 0), runners: make([]Runner, 0),
@@ -117,6 +118,12 @@ func NewBot(settings *BotSettings) *Bot {
} }
} }
u, err := bot.GetMe()
if err != nil {
bot.logger.Fatal(err)
}
bot.logger.Infof("Authorized as %s\n", u.FirstName)
return bot return bot
} }
@@ -244,9 +251,7 @@ func (b *Bot) Run() {
continue continue
} }
ctx := &MsgContext{ ctx := &MsgContext{Bot: b, Update: u}
Bot: b, Update: u,
}
for _, middleware := range b.middlewares { for _, middleware := range b.middlewares {
middleware.Execute(ctx, b.dbContext) middleware.Execute(ctx, b.dbContext)
} }

3
go.mod
View File

@@ -1,8 +1,9 @@
module git.nix13.pw/scuroneko/laniakea module git.nix13.pw/scuroneko/laniakea
go 1.25 go 1.25.6
require ( require (
git.nix13.pw/scuroneko/extypes v1.0.2
git.nix13.pw/scuroneko/slog v1.0.2 git.nix13.pw/scuroneko/slog v1.0.2
github.com/redis/go-redis/v9 v9.17.3 github.com/redis/go-redis/v9 v9.17.3
github.com/vinovest/sqlx v1.7.1 github.com/vinovest/sqlx v1.7.1

2
go.sum
View File

@@ -1,5 +1,7 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.nix13.pw/scuroneko/extypes v1.0.2 h1:Qz1InLccaB9crXY33oGrSetPHePKfQAUqW/p/iYXmJk=
git.nix13.pw/scuroneko/extypes v1.0.2/go.mod h1:uZVs8Yo3RrYAG9dMad6qR6lsYY67t+459D9c65QAYAw=
git.nix13.pw/scuroneko/slog v1.0.2 h1:vZyUROygxC2d5FJHUQM/30xFEHY1JT/aweDZXA4rm2g= git.nix13.pw/scuroneko/slog v1.0.2 h1:vZyUROygxC2d5FJHUQM/30xFEHY1JT/aweDZXA4rm2g=
git.nix13.pw/scuroneko/slog v1.0.2/go.mod h1:3Qm2wzkR5KjwOponMfG7TcGSDjmYaFqRAmLvSPTuWJI= git.nix13.pw/scuroneko/slog v1.0.2/go.mod h1:3Qm2wzkR5KjwOponMfG7TcGSDjmYaFqRAmLvSPTuWJI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=

View File

@@ -39,6 +39,9 @@ func (b *Bot) handleMessage(update *Update, ctx *MsgContext) {
ctx.Text = strings.TrimSpace(text[len(cmd):]) ctx.Text = strings.TrimSpace(text[len(cmd):])
ctx.Args = strings.Split(ctx.Text, " ") ctx.Args = strings.Split(ctx.Text, " ")
if !plugin.executeMiddlewares(ctx, b.dbContext) {
return
}
go plugin.Execute(cmd, ctx, b.dbContext) go plugin.Execute(cmd, ctx, b.dbContext)
return return
} }
@@ -65,6 +68,10 @@ func (b *Bot) handleCallback(update *Update, ctx *MsgContext) {
if !ok { if !ok {
continue continue
} }
if !plugin.executeMiddlewares(ctx, b.dbContext) {
return
}
go plugin.ExecutePayload(data.Command, ctx, b.dbContext) go plugin.ExecutePayload(data.Command, ctx, b.dbContext)
return return
} }

View File

@@ -195,3 +195,49 @@ func (b *Bot) SendChatAction(params SendChatActionP) (bool, error) {
} }
return *res, err return *res, err
} }
type SetMessageReactionP struct {
ChatId int `json:"chat_id"`
MessageId int `json:"message_id"`
IsBig bool `json:"is_big,omitempty"`
}
type SetMessageReactionEmojiP struct {
SetMessageReactionP
Reaction []ReactionTypeEmoji `json:"reaction"`
}
func (b *Bot) SetMessageReactionEmoji(params SetMessageReactionEmojiP) (bool, error) {
req := NewRequest[bool]("setMessageReaction", params)
res, err := req.Do(b)
if err != nil {
return false, err
}
return *res, err
}
type SetMessageReactionCustomEmojiP struct {
SetMessageReactionP
Reaction []ReactionTypeCustomEmoji `json:"reaction"`
}
func (b *Bot) SetMessageReactionCustom(params SetMessageReactionCustomEmojiP) (bool, error) {
req := NewRequest[bool]("setMessageReaction", params)
res, err := req.Do(b)
if err != nil {
return false, err
}
return *res, err
}
type SetMessageReactionPaidP struct {
SetMessageReactionP
}
func (b *Bot) SetMessageReactionPaid(params SetMessageReactionPaidP) (bool, error) {
req := NewRequest[bool]("setMessageReaction", params)
res, err := req.Do(b)
if err != nil {
return false, err
}
return *res, err
}

View File

@@ -1,6 +1,10 @@
package laniakea package laniakea
import "log" import (
"log"
"git.nix13.pw/scuroneko/extypes"
)
type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext) type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext)
@@ -9,6 +13,7 @@ type PluginBuilder struct {
commands map[string]*CommandExecutor commands map[string]*CommandExecutor
payloads map[string]*CommandExecutor payloads map[string]*CommandExecutor
updateListener *CommandExecutor updateListener *CommandExecutor
middlewares extypes.Slice[*PluginMiddleware]
} }
type Plugin struct { type Plugin struct {
@@ -16,6 +21,7 @@ type Plugin struct {
Commands map[string]*CommandExecutor Commands map[string]*CommandExecutor
Payloads map[string]*CommandExecutor Payloads map[string]*CommandExecutor
UpdateListener *CommandExecutor UpdateListener *CommandExecutor
Middlewares extypes.Slice[*PluginMiddleware]
} }
func NewPlugin(name string) *PluginBuilder { func NewPlugin(name string) *PluginBuilder {
@@ -45,6 +51,11 @@ func (p *PluginBuilder) UpdateListener(listener CommandExecutor) *PluginBuilder
return p return p
} }
func (p *PluginBuilder) Middleware(middleware *PluginMiddleware) *PluginBuilder {
p.middlewares = p.middlewares.Push(middleware)
return p
}
func (p *PluginBuilder) Build() Plugin { func (p *PluginBuilder) Build() Plugin {
if len(p.commands) == 0 && len(p.payloads) == 0 { if len(p.commands) == 0 && len(p.payloads) == 0 {
log.Println("no command or payloads") log.Println("no command or payloads")
@@ -54,6 +65,7 @@ func (p *PluginBuilder) Build() Plugin {
Commands: p.commands, Commands: p.commands,
Payloads: p.payloads, Payloads: p.payloads,
UpdateListener: p.updateListener, UpdateListener: p.updateListener,
Middlewares: p.middlewares,
} }
} }
@@ -65,6 +77,15 @@ func (p *Plugin) ExecutePayload(payload string, ctx *MsgContext, dbContext *Data
(*p.Payloads[payload])(ctx, dbContext) (*p.Payloads[payload])(ctx, dbContext)
} }
func (p *Plugin) executeMiddlewares(ctx *MsgContext, db *DatabaseContext) bool {
for _, m := range p.Middlewares {
if !m.Execute(ctx, db) {
return false
}
}
return true
}
type Middleware struct { type Middleware struct {
Name string Name string
Executor CommandExecutor Executor CommandExecutor
@@ -78,8 +99,8 @@ type MiddlewareBuilder struct {
async bool async bool
} }
func NewMiddleware(name string) *MiddlewareBuilder { func NewMiddleware(name string, executor CommandExecutor) *MiddlewareBuilder {
return &MiddlewareBuilder{name: name, async: false} return &MiddlewareBuilder{name: name, executor: executor, order: 0, async: false}
} }
func (m *MiddlewareBuilder) SetName(name string) *MiddlewareBuilder { func (m *MiddlewareBuilder) SetName(name string) *MiddlewareBuilder {
m.name = name m.name = name
@@ -112,3 +133,36 @@ func (m Middleware) Execute(ctx *MsgContext, db *DatabaseContext) {
m.Execute(ctx, db) m.Execute(ctx, db)
} }
} }
type PluginMiddlewareExecutor func(ctx *MsgContext, db *DatabaseContext) bool
// PluginMiddleware
// When async, returned value ignored
type PluginMiddleware struct {
executor PluginMiddlewareExecutor
order int
async bool
}
func NewPluginMiddleware(executor PluginMiddlewareExecutor) *PluginMiddleware {
return &PluginMiddleware{
executor: executor,
order: 0,
async: false,
}
}
func (m *PluginMiddleware) SetOrder(order int) *PluginMiddleware {
m.order = order
return m
}
func (m *PluginMiddleware) SetAsync(async bool) *PluginMiddleware {
m.async = async
return m
}
func (m *PluginMiddleware) Execute(ctx *MsgContext, db *DatabaseContext) bool {
if m.async {
go m.executor(ctx, db)
return true
}
return m.executor(ctx, db)
}

View File

@@ -1,71 +0,0 @@
package laniakea
import (
"errors"
"sync"
)
var QueueFullErr = errors.New("queue full")
type Queue[T any] struct {
size uint64
mu sync.RWMutex
queue []T
}
func CreateQueue[T any](size uint64) *Queue[T] {
return &Queue[T]{
queue: make([]T, 0),
size: size,
}
}
func (q *Queue[T]) Enqueue(el T) error {
if q.IsFull() {
return QueueFullErr
}
q.queue = append(q.queue, el)
return nil
}
func (q *Queue[T]) Peak() T {
q.mu.RLock()
defer q.mu.RUnlock()
return q.queue[0]
}
func (q *Queue[T]) IsEmpty() bool {
return q.Length() == 0
}
func (q *Queue[T]) IsFull() bool {
return q.Length() == q.size
}
func (q *Queue[T]) Length() uint64 {
q.mu.RLock()
defer q.mu.RUnlock()
return uint64(len(q.queue))
}
func (q *Queue[T]) Dequeue() T {
q.mu.RLock()
el := q.queue[0]
q.mu.RUnlock()
if q.Length() == 1 {
q.mu.Lock()
q.queue = make([]T, 0)
q.mu.Unlock()
return el
}
q.mu.Lock()
q.queue = q.queue[1:]
q.mu.Unlock()
return el
}
func (q *Queue[T]) Raw() []T {
return q.queue
}

View File

@@ -1,70 +0,0 @@
package laniakea
type Slice[T any] []T
func NewSliceFrom[T any](slice []T) Slice[T] {
s := make(Slice[T], len(slice))
copy(s[:], slice)
return s
}
func (s Slice[T]) Len() int {
return len(s)
}
func (s Slice[T]) Cap() int {
return cap(s)
}
func (s Slice[T]) Get(index int) T {
return s[index]
}
func (s Slice[T]) Last() T {
return s.Get(s.Len() - 1)
}
func (s Slice[T]) Swap(i, j int) Slice[T] {
s[i], s[j] = s[j], s[i]
return s
}
func (s Slice[T]) Filter(f func(e T) bool) Slice[T] {
out := make(Slice[T], 0)
for _, v := range s {
if f(v) {
out = append(out, v)
}
}
return out
}
func (s Slice[T]) Map(f func(e T) T) Slice[T] {
out := make(Slice[T], s.Len())
for i, v := range s {
out[i] = f(v)
}
return out
}
func (s Slice[T]) Pop(index int) Slice[T] {
if index == 0 {
return s[1:]
}
out := make(Slice[T], s.Len()-index)
for i, e := range s {
if i == index {
continue
}
out[i] = e
}
return out
}
func (s Slice[T]) Push(e T) Slice[T] {
return append(s, e)
}
func (s Slice[T]) ToArray() []T {
out := make([]T, len(s))
copy(out, s)
return out
}
func (s Slice[T]) ToAnyArray() []any {
out := make([]any, len(s))
for i, v := range s {
out[i] = v
}
return out
}

View File

@@ -1,5 +1,7 @@
package laniakea package laniakea
import "git.nix13.pw/scuroneko/extypes"
type Update struct { type Update struct {
UpdateID int `json:"update_id"` UpdateID int `json:"update_id"`
Message *Message `json:"message"` Message *Message `json:"message"`
@@ -54,7 +56,7 @@ type Message struct {
Chat *Chat `json:"chat,omitempty"` Chat *Chat `json:"chat,omitempty"`
Text string `json:"text"` Text string `json:"text"`
Photo Slice[*PhotoSize] `json:"photo,omitempty"` Photo extypes.Slice[*PhotoSize] `json:"photo,omitempty"`
Caption string `json:"caption,omitempty"` Caption string `json:"caption,omitempty"`
ReplyToMessage *Message `json:"reply_to_message"` ReplyToMessage *Message `json:"reply_to_message"`

View File

@@ -1,8 +1,8 @@
package laniakea package laniakea
const ( const (
VersionString = "0.3.7" VersionString = "0.3.9"
VersionMajor = 0 VersionMajor = 0
VersionMinor = 3 VersionMinor = 3
VersionPatch = 7 VersionPatch = 9
) )