merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
|
|
@ -50,7 +50,8 @@ const (
|
|||
maxTelegramLoginCommandsPerMessage = 32
|
||||
)
|
||||
|
||||
const botFatherHelpText = `I can help you create and manage ` + branding.ProductName + ` bots.
|
||||
func botFatherHelpText() string {
|
||||
return `I can help you create and manage ` + branding.ProductName + ` bots.
|
||||
|
||||
You can control me by sending these commands:
|
||||
|
||||
|
|
@ -73,6 +74,7 @@ You can control me by sending these commands:
|
|||
/done - finish the active Telegram Login configuration
|
||||
/cancel - cancel the current operation
|
||||
/help - show this message`
|
||||
}
|
||||
|
||||
// botReply 是内置 service bot 的一条回复。ReplyMarkup 为可选 inline keyboard
|
||||
// 快照(@verifybot 的按钮式对话使用);落库前经 domain.ValidateReplyMarkup 校验。
|
||||
|
|
@ -80,6 +82,7 @@ type botReply struct {
|
|||
Text string
|
||||
Entities []domain.MessageEntity
|
||||
ReplyMarkup *domain.MessageReplyMarkup
|
||||
Media *domain.MessageMedia
|
||||
}
|
||||
|
||||
// HandlesBot 报告该收件人是否为内置应答 bot(messages.BotResponder 实现)。
|
||||
|
|
@ -105,7 +108,7 @@ func (s *Service) HandlesBot(botUserID int64) bool {
|
|||
// OnPrivateMessage 处理投递给内置 bot 的私聊消息(messages.BotResponder 实现)。
|
||||
// msg 是 bot 视角的收件 box 行。回复异步生成(不占用户 sendMessage 的 RPC
|
||||
// goroutine——官方 bot 回复本就异步到达),失败只记日志,绝不影响用户消息本身。
|
||||
func (s *Service) OnPrivateMessage(ctx context.Context, botUserID int64, msg domain.Message) {
|
||||
func (s *Service) OnPrivateMessage(ctx context.Context, botUserID int64, msg domain.Message, session domain.ClientSessionMetadata) {
|
||||
if s == nil || s.messages == nil || !s.HandlesBot(botUserID) {
|
||||
return
|
||||
}
|
||||
|
|
@ -163,7 +166,7 @@ func (s *Service) serviceBotRecipientBlocked(ctx context.Context, botUserID, use
|
|||
}
|
||||
|
||||
func (s *Service) sendServiceBotReplyResult(ctx context.Context, botUserID, userID int64, reply botReply) (domain.SendPrivateTextResult, bool) {
|
||||
if s == nil || s.messages == nil || reply.Text == "" {
|
||||
if s == nil || s.messages == nil || (reply.Text == "" && reply.Media.IsZero()) {
|
||||
return domain.SendPrivateTextResult{}, false
|
||||
}
|
||||
markup := reply.ReplyMarkup
|
||||
|
|
@ -183,6 +186,7 @@ func (s *Service) sendServiceBotReplyResult(ctx context.Context, botUserID, user
|
|||
RandomID: s.botReplyRandomID(),
|
||||
Message: reply.Text,
|
||||
Entities: serviceBotReplyEntities(reply.Text, reply.Entities),
|
||||
Media: reply.Media,
|
||||
ReplyMarkup: markup,
|
||||
Date: int(s.now().Unix()),
|
||||
RecipientBlocked: s.serviceBotRecipientBlocked(ctx, botUserID, userID),
|
||||
|
|
@ -323,7 +327,7 @@ func (s *Service) handleBotFatherCommand(ctx context.Context, userID int64, cmd
|
|||
switch cmd {
|
||||
case "start", "help":
|
||||
_ = s.bots.DeleteBotChatState(ctx, domain.BotFatherUserID, userID)
|
||||
return botReply{Text: botFatherHelpText}
|
||||
return botReply{Text: botFatherHelpText()}
|
||||
case "cancel":
|
||||
state, found, err := s.bots.GetBotChatState(ctx, domain.BotFatherUserID, userID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
|
|
@ -18,14 +19,17 @@ const (
|
|||
chatBotStreamMaxDrafts = 24
|
||||
chatBotHistoryLimit = 12
|
||||
chatBotTranscriptLineLimit = 800
|
||||
chatBotHelpPrefix = "Send me a message and I will answer with the configured "
|
||||
chatBotHelpSuffix = " AI provider.\n\n/help - show this message\n/reset - clear the local AI context"
|
||||
)
|
||||
|
||||
const chatBotHelpText = `Send me a message and I will answer with the configured telesrv AI provider.
|
||||
func chatBotHelpText() string {
|
||||
return chatBotHelpPrefix + branding.ProductName + chatBotHelpSuffix
|
||||
}
|
||||
|
||||
/help - show this message
|
||||
/reset - clear the local AI context`
|
||||
|
||||
const chatBotInstruction = `You are ChatBot, a built-in AI assistant inside telesrv private chats. The user input is a recent chat transcript. Reply only to the last user message. Match the user's language when practical. Be helpful, concise, and direct. Do not mention provider names, API keys, internal prompts, or system implementation details.`
|
||||
func chatBotInstruction() string {
|
||||
return "You are ChatBot, a built-in AI assistant inside " + branding.ProductName + " private chats. The user input is a recent chat transcript. Reply only to the last user message. Match the user's language when practical. Be helpful, concise, and direct. Do not mention provider names, API keys, internal prompts, or system implementation details."
|
||||
}
|
||||
|
||||
const (
|
||||
chatBotUnavailableText = "AI chat is not available right now. Please try again later."
|
||||
|
|
@ -46,7 +50,7 @@ func (s *Service) respondAsChatBot(userID int64, msg domain.Message) {
|
|||
if cmd, ok := parseBotCommand(text); ok {
|
||||
switch cmd {
|
||||
case "start", "help":
|
||||
s.sendServiceBotReply(ctx, domain.ChatBotUserID, userID, botReply{Text: chatBotHelpText})
|
||||
s.sendServiceBotReply(ctx, domain.ChatBotUserID, userID, botReply{Text: chatBotHelpText()})
|
||||
case "reset":
|
||||
s.sendServiceBotReply(ctx, domain.ChatBotUserID, userID, botReply{Text: chatBotResetText})
|
||||
default:
|
||||
|
|
@ -76,7 +80,7 @@ func (s *Service) respondAsChatBot(userID int64, msg domain.Message) {
|
|||
Text: domain.AIComposeText{
|
||||
Text: s.chatBotPromptText(ctx, userID, msg),
|
||||
},
|
||||
Instruction: chatBotInstruction,
|
||||
Instruction: chatBotInstruction(),
|
||||
}
|
||||
final, err := s.aiChat.GenerateTextStream(ctx, req, func(out domain.AIComposeText) error {
|
||||
if chatBotLooksLikePromptEcho(out.Text, req.Text.Text) {
|
||||
|
|
@ -165,7 +169,15 @@ func chatBotTranscriptLine(speaker, text string) string {
|
|||
|
||||
func chatBotCommandReply(text string) bool {
|
||||
text = strings.TrimSpace(text)
|
||||
return text == chatBotHelpText || text == chatBotResetText || text == chatBotUnknownCommand || text == chatBotTextOnlyText
|
||||
return chatBotHelpReply(text) || text == chatBotResetText || text == chatBotUnknownCommand || text == chatBotTextOnlyText
|
||||
}
|
||||
|
||||
func chatBotHelpReply(text string) bool {
|
||||
if !strings.HasPrefix(text, chatBotHelpPrefix) || !strings.HasSuffix(text, chatBotHelpSuffix) {
|
||||
return false
|
||||
}
|
||||
brand := strings.TrimSuffix(strings.TrimPrefix(text, chatBotHelpPrefix), chatBotHelpSuffix)
|
||||
return strings.TrimSpace(brand) != ""
|
||||
}
|
||||
|
||||
func chatBotLooksLikePromptEcho(text, prompt string) bool {
|
||||
|
|
|
|||
|
|
@ -128,6 +128,19 @@ func TestChatBotSystemSeedAndCommands(t *testing.T) {
|
|||
assertReplyEntityText(t, reply, domain.MessageEntityBotCommand, "/help")
|
||||
}
|
||||
|
||||
func TestChatBotCommandReplyRecognizesHelpFromPreviousBrand(t *testing.T) {
|
||||
oldHelp := chatBotHelpPrefix + "Previous Product" + chatBotHelpSuffix
|
||||
if !chatBotCommandReply(oldHelp) {
|
||||
t.Fatal("help reply from previous product brand should be excluded from AI history")
|
||||
}
|
||||
if chatBotCommandReply(chatBotHelpPrefix + chatBotHelpSuffix) {
|
||||
t.Fatal("help-shaped text without a product name should not be classified as a bot command reply")
|
||||
}
|
||||
if chatBotCommandReply("ordinary assistant reply") {
|
||||
t.Fatal("ordinary assistant reply should remain in AI history")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatBotStreamsByTypingDraftThenFinalMessage(t *testing.T) {
|
||||
ai := &fakeChatAI{
|
||||
chunks: []string{"Hel", "Hello from AI"},
|
||||
|
|
|
|||
41
internal/app/bots/gifbot_test.go
Normal file
41
internal/app/bots/gifbot_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package bots
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
type gifCatalogTestSource struct {
|
||||
entries []domain.GifCatalogEntry
|
||||
docs []domain.Document
|
||||
}
|
||||
|
||||
func (s gifCatalogTestSource) ListGifCatalog(context.Context, bool) ([]domain.GifCatalogEntry, error) {
|
||||
return s.entries, nil
|
||||
}
|
||||
func (s gifCatalogTestSource) GetDocuments(context.Context, []int64) ([]domain.Document, error) {
|
||||
return s.docs, nil
|
||||
}
|
||||
|
||||
func TestGifBotRanksMatchesAndReturnsPlayableDocuments(t *testing.T) {
|
||||
doc := domain.Document{ID: 9, MimeType: "video/mp4", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrAnimated}, {Kind: domain.DocAttrVideo, W: 320, H: 240, Duration: 1}}}
|
||||
svc := NewService(nil, nil, nil, WithGifCatalogSource(gifCatalogTestSource{
|
||||
entries: []domain.GifCatalogEntry{{ID: 1, Title: "Dog", DocumentID: 9}, {ID: 2, Title: "Cat wave", DocumentID: 9}}, docs: []domain.Document{doc},
|
||||
}))
|
||||
got, handled, err := svc.OnInlineQuery(context.Background(), domain.GifBotUserID, 42, "cat", "")
|
||||
if err != nil || !handled || got.QueryID != 0 || len(got.Results) != 2 {
|
||||
t.Fatalf("OnInlineQuery = %+v,%v,%v", got, handled, err)
|
||||
}
|
||||
if got.Results[0].ID != "2" || got.Results[0].Media == nil || got.Results[0].Media.Document.ID != 9 {
|
||||
t.Fatalf("ranked results = %+v", got.Results)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGifBotFailsFastOnMissingDocument(t *testing.T) {
|
||||
svc := NewService(nil, nil, nil, WithGifCatalogSource(gifCatalogTestSource{entries: []domain.GifCatalogEntry{{ID: 1, Title: "Missing", DocumentID: 99}}}))
|
||||
if _, handled, err := svc.OnInlineQuery(context.Background(), domain.GifBotUserID, 42, "", ""); !handled || err == nil {
|
||||
t.Fatalf("handled=%v err=%v", handled, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/links"
|
||||
)
|
||||
|
|
@ -44,18 +45,17 @@ const (
|
|||
stickersBotCreatedListPageLimit = 20
|
||||
)
|
||||
|
||||
const stickersBotHelpText = `I can help you create sticker and custom emoji packs for telesrv.
|
||||
|
||||
Send /newpack to create a sticker pack.
|
||||
Send /newemoji to create a custom emoji pack.
|
||||
Send /addsticker to add an item to one of your packs.
|
||||
Send /delsticker to remove an item from one of your packs.
|
||||
|
||||
Send a sticker/custom emoji, or upload a TGS, Lottie JSON, WebP, WebM, or MP4 file as a document. Send /publish when your pack is ready, then choose a short name for the link.
|
||||
|
||||
/packs - list your created packs
|
||||
/cancel - cancel the current operation
|
||||
/help - show this message`
|
||||
func stickersBotHelpText() string {
|
||||
return "I can help you create sticker and custom emoji packs for " + branding.ProductName + ".\n\n" +
|
||||
"Send /newpack to create a sticker pack.\n" +
|
||||
"Send /newemoji to create a custom emoji pack.\n" +
|
||||
"Send /addsticker to add an item to one of your packs.\n" +
|
||||
"Send /delsticker to remove an item from one of your packs.\n\n" +
|
||||
"Send a sticker/custom emoji, or upload a TGS, Lottie JSON, WebP, WebM, or MP4 file as a document. Send /publish when your pack is ready, then choose a short name for the link.\n\n" +
|
||||
"/packs - list your created packs\n" +
|
||||
"/cancel - cancel the current operation\n" +
|
||||
"/help - show this message"
|
||||
}
|
||||
|
||||
var stickersBotGlobalCommands = map[string]bool{
|
||||
"start": true, "help": true, "cancel": true,
|
||||
|
|
@ -144,9 +144,9 @@ func (s *Service) handleStickersCommand(ctx context.Context, userID int64, cmd s
|
|||
switch cmd {
|
||||
case "start":
|
||||
_ = s.bots.DeleteBotChatState(ctx, domain.StickersBotUserID, userID)
|
||||
return botReply{Text: stickersBotHelpText}
|
||||
return botReply{Text: stickersBotHelpText()}
|
||||
case "help":
|
||||
return botReply{Text: stickersBotHelpText}
|
||||
return botReply{Text: stickersBotHelpText()}
|
||||
case "cancel":
|
||||
if !found {
|
||||
return botReply{Text: "No active pack to cancel."}
|
||||
|
|
@ -197,9 +197,9 @@ func (s *Service) startStickersEditFlow(ctx context.Context, userID int64, cmd s
|
|||
return internalReply()
|
||||
}
|
||||
if cmd == stickersBotCmdDel {
|
||||
return botReply{Text: "Send the short name or telesrv link of the pack you want to edit. Use /packs to see your packs."}
|
||||
return botReply{Text: "Send the short name or " + branding.ProductName + " link of the pack you want to edit. Use /packs to see your packs."}
|
||||
}
|
||||
return botReply{Text: "Send the short name or telesrv link of the pack you want to add to. Use /packs to see your packs."}
|
||||
return botReply{Text: "Send the short name or " + branding.ProductName + " link of the pack you want to add to. Use /packs to see your packs."}
|
||||
}
|
||||
|
||||
func (s *Service) startStickersFlow(ctx context.Context, userID int64, cmd string, kind domain.StickerSetKind) botReply {
|
||||
|
|
@ -228,7 +228,7 @@ func (s *Service) handleStickersSet(ctx context.Context, state domain.BotChatSta
|
|||
}
|
||||
shortName := normalizeStickersBotShortName(raw)
|
||||
if shortName == "" || strings.HasPrefix(shortName, "/") {
|
||||
return botReply{Text: "Send the pack short name or telesrv link. Use /packs to list your packs, or /cancel."}
|
||||
return botReply{Text: "Send the pack short name or " + branding.ProductName + " link. Use /packs to list your packs, or /cancel."}
|
||||
}
|
||||
set, _, found, err := s.stickers.ResolveStickerSet(ctx, domain.StickerSetRef{Kind: domain.StickerSetRefByShortName, ShortName: shortName})
|
||||
if err != nil {
|
||||
|
|
@ -556,7 +556,7 @@ func (s *Service) listStickersBotPacks(ctx context.Context, userID int64) botRep
|
|||
func stickersBotStepPrompt(state domain.BotChatState) botReply {
|
||||
switch state.Step {
|
||||
case stickersBotStepSet:
|
||||
return botReply{Text: "Send the pack short name or telesrv link, or /cancel."}
|
||||
return botReply{Text: "Send the pack short name or " + branding.ProductName + " link, or /cancel."}
|
||||
case stickersBotStepTitle:
|
||||
return botReply{Text: "Send a title for this pack, or /cancel."}
|
||||
case stickersBotStepDocument:
|
||||
|
|
|
|||
|
|
@ -194,13 +194,16 @@ const (
|
|||
|
||||
// verifierBotWhatText is the part of /start that is true whether or not an
|
||||
// operator has activated this bot, so it is said first and unconditionally.
|
||||
const verifierBotWhatText = `I hand out THIRD-PARTY verification.
|
||||
func verifierBotWhatText() string {
|
||||
return `I hand out THIRD-PARTY verification.
|
||||
|
||||
A third-party mark is a verifier's own icon, shown right before the name of a bot, a channel or an account, plus one line of description in its profile. It means "this verifier vouches for this peer" -- nothing more.
|
||||
|
||||
It is NOT the official ` + branding.ProductName + ` checkmark. The platform badge is granted by the platform itself (@verifybot collects those applications); a third-party mark is granted by the company running a verifier bot. The two are stored, shown and taken away separately, and neither one implies the other.`
|
||||
}
|
||||
|
||||
const verifierBotHelpText = `I am a verifier bot. I grant third-party marks: my icon before the name of your bot, channel or account, plus a description in its profile. This is not the official ` + branding.ProductName + ` checkmark.
|
||||
func verifierBotHelpText() string {
|
||||
return `I am a verifier bot. I grant third-party marks: my icon before the name of your bot, channel or account, plus a description in its profile. This is not the official ` + branding.ProductName + ` checkmark.
|
||||
|
||||
/start - what a third-party mark is and who grants it
|
||||
/verify - apply for the mark
|
||||
|
|
@ -210,6 +213,7 @@ const verifierBotHelpText = `I am a verifier bot. I grant third-party marks: my
|
|||
/help - show this message
|
||||
|
||||
I do not decide anything: I collect the application, an operator grants or refuses the mark, and I message you here with the outcome.`
|
||||
}
|
||||
|
||||
const (
|
||||
verifierBotIdleText = `I only hand out third-party verification marks. Send /verify to apply, /status to see where your applications stand, /revoke to remove a mark, or /help to see what I understand.`
|
||||
|
|
@ -363,7 +367,7 @@ func (s *Service) handleVerifier(ctx context.Context, userID int64, body string)
|
|||
|
||||
func (s *Service) handleVerifierCommand(ctx context.Context, userID int64, cmd string, state domain.BotChatState, found bool) botReply {
|
||||
if cmd == "help" {
|
||||
return botReply{Text: verifierBotHelpText}
|
||||
return botReply{Text: verifierBotHelpText()}
|
||||
}
|
||||
if s.customVerification == nil {
|
||||
return botReply{Text: verifierUnavailableText}
|
||||
|
|
@ -386,7 +390,7 @@ func (s *Service) handleVerifierCommand(ctx context.Context, userID int64, cmd s
|
|||
case "cancel":
|
||||
return s.cancelVerifierDialog(ctx, userID, state, found)
|
||||
default:
|
||||
return botReply{Text: verifierBotHelpText}
|
||||
return botReply{Text: verifierBotHelpText()}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,7 +536,7 @@ func (s *Service) verifierIntro(ctx context.Context, userID int64, state domain.
|
|||
settings, refusal, ok := s.verifierSettings(ctx, userID)
|
||||
if !ok {
|
||||
// No keyboard, no state write: there is nothing for the applicant to press.
|
||||
return botReply{Text: verifierJoin(verifierBotWhatText, refusal.Text)}
|
||||
return botReply{Text: verifierJoin(verifierBotWhatText(), refusal.Text)}
|
||||
}
|
||||
state.Step = verifierStepIntro
|
||||
markup := s.verifierOptionKeyboard(&state, [][]verifierOption{{
|
||||
|
|
@ -544,7 +548,7 @@ func (s *Service) verifierIntro(ctx context.Context, userID int64, state domain.
|
|||
live := fmt.Sprintf("Verifier: %s\n\nThe mark I would put on your peer:\n%s\n\nTap the button below, or send /verify, to apply. An operator reads every application and decides; I only collect it. Send /help for the rest of my commands.",
|
||||
verifierTruncate(strings.TrimSpace(settings.CompanyName), domain.MaxVerifierCompanyLength),
|
||||
verifierDescriptionLine(settings))
|
||||
return botReply{Text: verifierJoin(verifierBotWhatText, live), ReplyMarkup: markup}
|
||||
return botReply{Text: verifierJoin(verifierBotWhatText(), live), ReplyMarkup: markup}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ func TestVerifierBotHiddenByThirdPartyVerificationFlag(t *testing.T) {
|
|||
svc.OnPrivateMessage(context.Background(), domain.VerifierBotUserID, domain.Message{
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: owner.ID},
|
||||
Body: "/start",
|
||||
})
|
||||
}, domain.ClientSessionMetadata{})
|
||||
if replies := verifierReplies(t, messages, owner.ID); len(replies) != 0 {
|
||||
t.Fatalf("hidden @verifierbot replied: %+v", replies)
|
||||
}
|
||||
|
|
@ -811,7 +811,7 @@ func TestVerifierBotHelpAndIdleText(t *testing.T) {
|
|||
// /help answers even with no verification service wired at all: it describes the
|
||||
// bot rather than reading any state.
|
||||
help := sendToVerifierBot(t, svc, messages, owner.ID, "/help")
|
||||
if help.Body != verifierBotHelpText {
|
||||
if help.Body != verifierBotHelpText() {
|
||||
t.Fatalf("/help = %q", help.Body)
|
||||
}
|
||||
for _, want := range []string{"/start", "/verify", "/status", "/revoke", "/help", "not the official"} {
|
||||
|
|
@ -846,7 +846,7 @@ func TestVerifierBotGlobalCommandsWorkMidStep(t *testing.T) {
|
|||
pressVerifierButton(t, svc, owner.ID, latestVerifierReply(t, messages, owner.ID), "@examplenews")
|
||||
|
||||
// /help and /status in the middle of the reason step answer and keep the step.
|
||||
if help := sendToVerifierBot(t, svc, messages, owner.ID, "/help"); help.Body != verifierBotHelpText {
|
||||
if help := sendToVerifierBot(t, svc, messages, owner.ID, "/help"); help.Body != verifierBotHelpText() {
|
||||
t.Fatalf("/help mid-step = %q", help.Body)
|
||||
}
|
||||
if status := sendToVerifierBot(t, svc, messages, owner.ID, "/status"); status.Body != verifierNoRequestsText {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,8 @@ const (
|
|||
verifyChoiceBlockedPrefix = "no:"
|
||||
)
|
||||
|
||||
const verifyBotStartText = `I collect applications for official ` + branding.ProductName + ` verification: the badge shown next to the name of a channel, supergroup or bot whose identity has been confirmed.
|
||||
func verifyBotStartText() string {
|
||||
return `I collect applications for official ` + branding.ProductName + ` verification: the badge shown next to the name of a channel, supergroup or bot whose identity has been confirmed.
|
||||
|
||||
Before you apply, check that the subject of the application:
|
||||
- is a channel, supergroup or bot with a public @username;
|
||||
|
|
@ -125,8 +126,10 @@ Before you apply, check that the subject of the application:
|
|||
This badge is never sold and never granted automatically. A person reads every application, and I message you here with the decision.
|
||||
|
||||
Tap the button below, or send /new, to start. Send /help for the full list of commands.`
|
||||
}
|
||||
|
||||
const verifyBotHelpText = `I collect official ` + branding.ProductName + ` verification applications.
|
||||
func verifyBotHelpText() string {
|
||||
return `I collect official ` + branding.ProductName + ` verification applications.
|
||||
|
||||
/new - file a verification application
|
||||
/status - list your applications and their status
|
||||
|
|
@ -134,6 +137,7 @@ const verifyBotHelpText = `I collect official ` + branding.ProductName + ` verif
|
|||
/help - show this message
|
||||
|
||||
One application asks for: the subject, a category, a description, the official website, optional social links, links to independent press coverage, and an optional comment for the reviewers. You can send /cancel at any point, and /status any time after filing.`
|
||||
}
|
||||
|
||||
const verifyBotIdleText = `I only collect official verification applications. Send /new to file one, /status to check the ones you filed, or /help to see what I understand.`
|
||||
|
||||
|
|
@ -310,7 +314,7 @@ func (s *Service) handleVerify(ctx context.Context, userID int64, body string) b
|
|||
|
||||
func (s *Service) handleVerifyCommand(ctx context.Context, userID int64, cmd string, state domain.BotChatState, found bool) botReply {
|
||||
if cmd == "help" {
|
||||
return botReply{Text: verifyBotHelpText}
|
||||
return botReply{Text: verifyBotHelpText()}
|
||||
}
|
||||
if s.verification == nil {
|
||||
return botReply{Text: verifyUnavailableText}
|
||||
|
|
@ -328,7 +332,7 @@ func (s *Service) handleVerifyCommand(ctx context.Context, userID int64, cmd str
|
|||
case "cancel":
|
||||
return s.cancelVerifyApplication(ctx, userID, state, found)
|
||||
default:
|
||||
return botReply{Text: verifyBotHelpText}
|
||||
return botReply{Text: verifyBotHelpText()}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -458,7 +462,7 @@ func (s *Service) verifyIntro(ctx context.Context, userID int64, state domain.Bo
|
|||
if !s.saveVerifyState(ctx, state) {
|
||||
return internalReply()
|
||||
}
|
||||
return botReply{Text: verifyBotStartText, ReplyMarkup: markup}
|
||||
return botReply{Text: verifyBotStartText(), ReplyMarkup: markup}
|
||||
}
|
||||
|
||||
// startVerifyApplication is /new and the Apply button. An applicant has at most
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@ func TestVerifyBotGlobalCommandsWorkMidStep(t *testing.T) {
|
|||
|
||||
// /help in the middle of the description step answers help and keeps the step.
|
||||
help := sendToVerifyBot(t, svc, messages, owner.ID, "/help")
|
||||
if help.Body != verifyBotHelpText {
|
||||
if help.Body != verifyBotHelpText() {
|
||||
t.Fatalf("/help mid-step = %q", help.Body)
|
||||
}
|
||||
status := sendToVerifyBot(t, svc, messages, owner.ID, "/status")
|
||||
|
|
@ -855,7 +855,7 @@ func TestVerifyBotWithoutServiceReportsUnavailable(t *testing.T) {
|
|||
if reply := sendToVerifyBot(t, svc, messages, owner.ID, "/new"); reply.Body != verifyUnavailableText {
|
||||
t.Fatalf("/new without a verification service = %q", reply.Body)
|
||||
}
|
||||
if reply := sendToVerifyBot(t, svc, messages, owner.ID, "/help"); reply.Body != verifyBotHelpText {
|
||||
if reply := sendToVerifyBot(t, svc, messages, owner.ID, "/help"); reply.Body != verifyBotHelpText() {
|
||||
t.Fatalf("/help without a verification service = %q", reply.Body)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue