mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-06-09 10:13:59 +00:00
f3b9891a54
Rename: - Go module github.com/tiennm99/miti99bot-go → github.com/tiennm99/miti99bot - CloudFormation stack miti99bot-aws-port → miti99bot - Drop "port", "Cloud Run", "GCP", "cutover", "Phase NN" framing from active code and docs — project reads as canonical AWS-Lambda from now on. AWS deploy guide + flow fix: - New docs/deploy-aws-free-tier-guide.md — Ubuntu 24.04 ARM64 onboarding with project-local venv (pip awscli + sam-cli), SSM secrets via read -s, idempotent OIDC provider + role creation, $1 budget alarm. - Drop sam build from the pipeline — provided.al2023 + makefile builder expects a Makefile in CodeUri (build/lambda/, the output dir), so the step always fails. sam deploy --template-file template.yaml now reads the raw template and zips build/lambda/ directly. - Rollback section rewritten — use continue-update-rollback / cancel-update-stack / git-SHA redeploy. Drop the broken --use-previous-template recipe. - DynamoDB free-tier row corrected (on-demand is 2.5M read / 1M write request units, not 25 RCU/WCU). Updated: - README.md fully rewritten (drops port/legacy framing, lists modules, points new users at the free-tier guide). - aws/README.md retitled "AWS account setup", phase numbers stripped. - Makefile / .github/workflows/deploy.yml — sam deploy flow. - samconfig.toml — stack_name = "miti99bot". - Go comments — Cloud Run → Lambda, Cloud Scheduler → EventBridge Scheduler, Cloud Logging → CloudWatch Logs. - Struct field GCPProject → FirestoreProject (env GOOGLE_CLOUD_PROJECT unchanged). Plus advisory reports under plans/reports/ from the code-reviewer + researcher passes that informed the fixes. Verified: go vet ./..., go build ./..., go test ./... all green.
112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
package util
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"html"
|
|
"strings"
|
|
|
|
"github.com/go-telegram/bot"
|
|
"github.com/go-telegram/bot/models"
|
|
|
|
"github.com/tiennm99/miti99bot/internal/modules"
|
|
)
|
|
|
|
const repoURL = "https://github.com/tiennm99/miti99bot"
|
|
|
|
var supportFooter = fmt.Sprintf(
|
|
`Enjoying the bot? Support me by starring the repo: <a href="%s">%s</a>`,
|
|
repoURL, repoURL,
|
|
)
|
|
|
|
// RenderHelp produces the body of /help: each module's public + protected
|
|
// commands grouped under a bold module name, followed by the support footer.
|
|
// Modules in MODULES-env order. Modules with no visible commands are omitted.
|
|
// Private commands are always skipped.
|
|
//
|
|
// Exposed (capitalised) so tests can assert on the string without spinning up
|
|
// a bot context.
|
|
func RenderHelp(reg *modules.Registry) string {
|
|
if reg == nil {
|
|
return "no commands registered\n\n" + supportFooter
|
|
}
|
|
|
|
type entry struct {
|
|
name string
|
|
description string
|
|
protected bool
|
|
}
|
|
byModule := make(map[string][]entry, len(reg.Modules))
|
|
|
|
for _, c := range reg.PublicCommands() {
|
|
byModule[ownerOf(reg, c.Name)] = append(byModule[ownerOf(reg, c.Name)], entry{
|
|
name: c.Name, description: c.Description, protected: false,
|
|
})
|
|
}
|
|
for _, c := range reg.ProtectedCommands() {
|
|
byModule[ownerOf(reg, c.Name)] = append(byModule[ownerOf(reg, c.Name)], entry{
|
|
name: c.Name, description: c.Description, protected: true,
|
|
})
|
|
}
|
|
|
|
var sections []string
|
|
for _, mod := range reg.Modules {
|
|
es := byModule[mod.Name]
|
|
if len(es) == 0 {
|
|
continue
|
|
}
|
|
var sb strings.Builder
|
|
fmt.Fprintf(&sb, "<b>%s</b>", html.EscapeString(mod.Name))
|
|
for _, e := range es {
|
|
suffix := ""
|
|
if e.protected {
|
|
suffix = " (protected)"
|
|
}
|
|
fmt.Fprintf(&sb, "\n/%s — %s%s", e.name, html.EscapeString(e.description), suffix)
|
|
}
|
|
sections = append(sections, sb.String())
|
|
}
|
|
|
|
body := "no commands registered"
|
|
if len(sections) > 0 {
|
|
body = strings.Join(sections, "\n\n")
|
|
}
|
|
return body + "\n\n" + supportFooter
|
|
}
|
|
|
|
// ownerOf finds the module that registered the named command. Linear scan
|
|
// (modules are few; commands per module are few). Returns "" if not found —
|
|
// callers treat that as "skip".
|
|
func ownerOf(reg *modules.Registry, cmdName string) string {
|
|
for _, m := range reg.Modules {
|
|
for _, c := range m.Commands {
|
|
if c.Name == cmdName {
|
|
return m.Name
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// helpCommand returns /help — pure renderer over the registry.
|
|
func helpCommand(reg *modules.Registry) modules.Command {
|
|
return modules.Command{
|
|
Name: "help",
|
|
Visibility: modules.VisibilityPublic,
|
|
Description: "Show all available commands",
|
|
Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error {
|
|
if update.Message == nil {
|
|
return nil
|
|
}
|
|
text := RenderHelp(reg)
|
|
_, err := b.SendMessage(ctx, &bot.SendMessageParams{
|
|
ChatID: update.Message.Chat.ID,
|
|
Text: text,
|
|
ParseMode: models.ParseModeHTML,
|
|
LinkPreviewOptions: &models.LinkPreviewOptions{IsDisabled: bot.True()},
|
|
})
|
|
return err
|
|
},
|
|
}
|
|
}
|