Skip to content

Commit 63d094a

Browse files
Frederickfreddycodes23
authored andcommitted
feat: add Telegram bot integration
1 parent 2ec8ef3 commit 63d094a

4 files changed

Lines changed: 208 additions & 0 deletions

File tree

pkg/telegram/auth.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package telegram
2+
3+
type Auth struct {
4+
allowed map[int64]bool
5+
}
6+
7+
func NewAuth(ids []int64) *Auth {
8+
m := make(map[int64]bool, len(ids))
9+
for _, id := range ids {
10+
m[id] = true
11+
}
12+
return &Auth{allowed: m}
13+
}
14+
15+
func (a *Auth) IsAllowed(userID int64) bool {
16+
return a.allowed[userID]
17+
}

pkg/telegram/bot.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package telegram
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
8+
)
9+
10+
type Bot struct {
11+
api *tgbotapi.BotAPI
12+
handler *Handler
13+
auth *Auth
14+
stopCh chan struct{}
15+
}
16+
17+
func New(token string, allowedIDs []int64, svc FlowService) (*Bot, error) {
18+
api, err := tgbotapi.NewBotAPI(token)
19+
if err != nil {
20+
return nil, err
21+
}
22+
api.Debug = false
23+
log.Printf("Telegram bot authorized as @%s", api.Self.UserName)
24+
return &Bot{
25+
api: api,
26+
handler: NewHandler(api, svc),
27+
auth: NewAuth(allowedIDs),
28+
stopCh: make(chan struct{}),
29+
}, nil
30+
}
31+
32+
func (b *Bot) Start(ctx context.Context) {
33+
u := tgbotapi.NewUpdate(0)
34+
u.Timeout = 60
35+
updates := b.api.GetUpdatesChan(u)
36+
log.Println("Telegram bot polling for updates...")
37+
for {
38+
select {
39+
case update := <-updates:
40+
if update.Message == nil {
41+
continue
42+
}
43+
if !b.auth.IsAllowed(update.Message.From.ID) {
44+
log.Printf("Telegram: blocked user %d", update.Message.From.ID)
45+
continue
46+
}
47+
go b.handler.Handle(update.Message)
48+
case <-ctx.Done():
49+
return
50+
case <-b.stopCh:
51+
return
52+
}
53+
}
54+
}
55+
56+
func (b *Bot) Stop() {
57+
close(b.stopCh)
58+
b.api.StopReceivingUpdates()
59+
}

pkg/telegram/formatter.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package telegram
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
func FormatWelcome() string {
9+
return `*Welcome to PentAGI*
10+
11+
I am your AI-powered security assistant.
12+
13+
Send /help to see available commands.`
14+
}
15+
16+
func FormatHelp() string {
17+
return `*Available commands*
18+
19+
/flows — list your recent flows
20+
/new <task> — create a new flow
21+
/status <id> — check flow status
22+
/stop <id> — stop a running flow
23+
/help — show this message`
24+
}
25+
26+
func FormatFlowList(flows []Flow) string {
27+
if len(flows) == 0 {
28+
return "No flows found. Use /new <task> to create one."
29+
}
30+
var sb strings.Builder
31+
sb.WriteString("*Your flows:*\n\n")
32+
for _, f := range flows {
33+
sb.WriteString(fmt.Sprintf("• `%s` — %s (%s)\n", f.ID[:8], f.Title, f.Status))
34+
}
35+
return sb.String()
36+
}
37+
38+
func FormatFlowCreated(f *Flow) string {
39+
return fmt.Sprintf("*Flow created*\n\nID: `%s`\nTask: %s\nStatus: %s", f.ID, f.Title, f.Status)
40+
}
41+
42+
func FormatFlowStatus(f *Flow) string {
43+
return fmt.Sprintf("*Flow status*\n\nID: `%s`\nTask: %s\nStatus: %s", f.ID, f.Title, f.Status)
44+
}

pkg/telegram/handler.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package telegram
2+
3+
import (
4+
"fmt"
5+
6+
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
7+
)
8+
9+
type FlowService interface {
10+
ListFlows(userID int64) ([]Flow, error)
11+
CreateFlow(userID int64, task string) (*Flow, error)
12+
GetFlowStatus(flowID string) (*Flow, error)
13+
StopFlow(flowID string) error
14+
}
15+
16+
type Flow struct {
17+
ID string
18+
Title string
19+
Status string
20+
}
21+
22+
type Handler struct {
23+
api *tgbotapi.BotAPI
24+
svc FlowService
25+
}
26+
27+
func NewHandler(api *tgbotapi.BotAPI, svc FlowService) *Handler {
28+
return &Handler{api: api, svc: svc}
29+
}
30+
31+
func (h *Handler) Handle(msg *tgbotapi.Message) {
32+
switch msg.Command() {
33+
case "start":
34+
h.reply(msg, FormatWelcome())
35+
case "help":
36+
h.reply(msg, FormatHelp())
37+
case "flows":
38+
flows, err := h.svc.ListFlows(msg.From.ID)
39+
if err != nil {
40+
h.reply(msg, fmt.Sprintf("Error fetching flows: %v", err))
41+
return
42+
}
43+
h.reply(msg, FormatFlowList(flows))
44+
case "new":
45+
task := msg.CommandArguments()
46+
if task == "" {
47+
h.reply(msg, "Usage: /new <task description>")
48+
return
49+
}
50+
flow, err := h.svc.CreateFlow(msg.From.ID, task)
51+
if err != nil {
52+
h.reply(msg, fmt.Sprintf("Error creating flow: %v", err))
53+
return
54+
}
55+
h.reply(msg, FormatFlowCreated(flow))
56+
case "status":
57+
id := msg.CommandArguments()
58+
if id == "" {
59+
h.reply(msg, "Usage: /status <flow_id>")
60+
return
61+
}
62+
flow, err := h.svc.GetFlowStatus(id)
63+
if err != nil {
64+
h.reply(msg, fmt.Sprintf("Error: %v", err))
65+
return
66+
}
67+
h.reply(msg, FormatFlowStatus(flow))
68+
case "stop":
69+
id := msg.CommandArguments()
70+
if id == "" {
71+
h.reply(msg, "Usage: /stop <flow_id>")
72+
return
73+
}
74+
if err := h.svc.StopFlow(id); err != nil {
75+
h.reply(msg, fmt.Sprintf("Error: %v", err))
76+
return
77+
}
78+
h.reply(msg, "Flow stopped.")
79+
default:
80+
h.reply(msg, "Unknown command. Send /help for available commands.")
81+
}
82+
}
83+
84+
func (h *Handler) reply(msg *tgbotapi.Message, text string) {
85+
m := tgbotapi.NewMessage(msg.Chat.ID, text)
86+
m.ParseMode = tgbotapi.ModeMarkdown
87+
h.api.Send(m)
88+
}

0 commit comments

Comments
 (0)