mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-09-20 02:21:39 +00:00
/alias <name> saves a replied message under a name and /insert <name> sends it back; /aliases lists every name and /unalias deletes one. Every Telegram format is supported — sticker, photo, GIF, video, video note, audio, voice, document, plain text — and each is kept as the file_id Telegram already issued, so nothing is downloaded and an alias survives redeploys. The namespace is global and the last assignment wins, matching the shared sticker pack; /unalias is open to anyone for the same reason. A saved name also works as its own command: /cheer rather than /insert cheer. This needs two new seams in the module contract. Module.Fallback handles a /command no module registered, and the dispatcher installs it after every Command — the bot library returns the first matching handler, so code always beats a name resolved at runtime, including an alias that shares a command added in a later build. /alias refuses a name already in the registry for the same reason, since such an alias would only reach /insert. An unknown command stays silent: the fallback sees every unrecognised /foo in every chat, so replying would make typos noisy and would confirm which names exist. Module.Inline answers inline-mode queries — "@botname <prefix>" from any chat, filtered by prefix and capped at Telegram's 50 results. Each result is a cached inline type carrying the stored file_id, so the picker renders real previews without an upload. Video notes are omitted because Telegram defines no InlineQueryResultCachedVideoNote and substituting a plain video would change what was saved. Inline mode must be enabled in BotFather before Telegram delivers these updates. Both slots are single-occupancy with conflict detection at Build. Auth.Permits learns the inline sender so a gated inline handler would not deny everyone. Build's command indexing and slot claiming move into addCommands/addSingletons, keeping it under the project's cyclomatic cap. Also restores the sticker module: /addsticker moves back out of util, which has no store, into internal/modules/sticker as its only command.
95 lines
3.4 KiB
Go
95 lines
3.4 KiB
Go
// Package alias implements /alias and /insert: a shared, bot-wide dictionary
|
|
// mapping a short name to any Telegram message the bot has seen.
|
|
//
|
|
// The namespace is global on purpose — a name assigned in any chat works in
|
|
// every chat, for everyone, the same way the sticker pack /addsticker writes to
|
|
// is shared. That makes the store a plain map from name to content, with no
|
|
// chat or user component in the key.
|
|
//
|
|
// Nothing is downloaded. Every media kind is kept as the file_id Telegram
|
|
// already issued, and /insert hands that same id straight back to a send call,
|
|
// so the module stores bytes for nothing but the name and a caption.
|
|
package alias
|
|
|
|
import (
|
|
"github.com/tiennm99/miti99bot/internal/modules"
|
|
"github.com/tiennm99/miti99bot/internal/storage"
|
|
)
|
|
|
|
// Alias is one name-to-content binding.
|
|
//
|
|
// FileID is bot-scoped: Telegram file IDs are only valid for the bot that was
|
|
// shown them, which is no constraint here because the same bot always sends
|
|
// them back. It survives restarts and re-deploys — the id refers to a file on
|
|
// Telegram's servers, not to anything this bot holds.
|
|
type Alias struct {
|
|
Name string `bson:"name"` // as typed at assignment, for echoing back
|
|
Kind string `bson:"kind"` // one of the kind* constants
|
|
FileID string `bson:"fileId"` // the media; empty for kindText
|
|
Text string `bson:"text"` // message text for kindText, else the caption
|
|
OwnerID int64 `bson:"ownerId"` // who assigned it last
|
|
CreatedAt int64 `bson:"createdAt"` // unix millis
|
|
}
|
|
|
|
// Store is the module's typed view over its collection.
|
|
type Store = storage.DocStore[Alias]
|
|
|
|
// state holds what the handlers share.
|
|
type state struct {
|
|
store Store
|
|
// reg resolves whether a name is already a real command. Captured rather
|
|
// than snapshotted: at factory time the registry holds only the modules
|
|
// ahead of this one in MODULES order, and by the time a handler runs it is
|
|
// complete.
|
|
reg *modules.Registry
|
|
}
|
|
|
|
// New is the module Factory.
|
|
func New(deps modules.Deps) modules.Module {
|
|
s := &state{store: storage.Typed[Alias](deps.Store), reg: deps.Registry}
|
|
return modules.Module{
|
|
// Makes a saved name invocable directly — /cheer rather than
|
|
// /insert cheer. Registered after every command by the dispatcher, so
|
|
// it can never shadow one.
|
|
Fallback: &modules.CommandFallback{
|
|
Visibility: modules.VisibilityPublic,
|
|
Handler: s.handleFallback,
|
|
},
|
|
// "@botname <prefix>" in any chat, with previews. Requires inline mode
|
|
// enabled in BotFather; see docs/aliases.md.
|
|
Inline: &modules.InlineQuery{
|
|
Visibility: modules.VisibilityPublic,
|
|
Handler: s.handleInline,
|
|
},
|
|
Commands: []modules.Command{
|
|
{
|
|
Name: "alias",
|
|
Visibility: modules.VisibilityPublic,
|
|
Description: "Reply to a message to save it under a name",
|
|
Parameters: "<name>",
|
|
Handler: s.handleAlias,
|
|
},
|
|
{
|
|
Name: "insert",
|
|
Visibility: modules.VisibilityPublic,
|
|
Description: "Send back whatever is saved under a name",
|
|
Parameters: "<name>",
|
|
Handler: s.handleInsert,
|
|
},
|
|
{
|
|
Name: "aliases",
|
|
Visibility: modules.VisibilityPublic,
|
|
Description: "List every saved alias name",
|
|
Handler: s.handleAliases,
|
|
},
|
|
{
|
|
Name: "unalias",
|
|
Visibility: modules.VisibilityPublic,
|
|
Description: "Delete a saved alias",
|
|
Parameters: "<name>",
|
|
Handler: s.handleUnalias,
|
|
},
|
|
},
|
|
}
|
|
}
|