mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-20 02:18:23 +00:00
feat(misc): add /trongtruonghop disclaimer command
Vietnamese disclaimer one-liner. Interpolates a target name (default "VNG") and the sender — @username when set, else tg://user?id link with the display name. User-supplied text HTML-escaped; reply routed through ReplyHTML to keep forum-topic threading.
This commit is contained in:
1 parent
3dc6248726
commit
ee5a0cc954
7 files changed
+393
-4
No files matched your search
@@ -6,6 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-telegram/bot/models"
|
||||
|
||||
"github.com/tiennm99/miti99bot/internal/modules"
|
||||
"github.com/tiennm99/miti99bot/internal/storage"
|
||||
"github.com/tiennm99/miti99bot/internal/testutil"
|
||||
@@ -86,6 +88,83 @@ func TestMstats_DeniedToNonAdmin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// trongTruongHopUpdate is the inline counterpart of testutil.NewPrivateMessage
|
||||
// for cases that need control over From (username, names). The dispatcher
|
||||
// requires a bot_command entity, so we lift that from the helper API by reusing
|
||||
// NewPrivateMessage and overwriting From.
|
||||
func trongTruongHopUpdate(t *testing.T, text string, from *models.User) *models.Update {
|
||||
t.Helper()
|
||||
u := testutil.NewPrivateMessage(from.ID, text)
|
||||
u.Message.From = from
|
||||
return u
|
||||
}
|
||||
|
||||
func TestTrongTruongHop_DefaultArgUsesVNG(t *testing.T) {
|
||||
rb, _ := installMisc(t, 999)
|
||||
rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop",
|
||||
&models.User{ID: 7, Username: "boss", FirstName: "Boss"}))
|
||||
|
||||
got := rb.LastSent().Text()
|
||||
if !strings.Contains(got, "VNG") {
|
||||
t.Errorf("reply missing default target VNG: %q", got)
|
||||
}
|
||||
if n := strings.Count(got, "@boss"); n != 2 {
|
||||
t.Errorf("reply mentions @boss %d times, want 2: %q", n, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrongTruongHop_CustomArg(t *testing.T) {
|
||||
rb, _ := installMisc(t, 999)
|
||||
rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop Acme Corp",
|
||||
&models.User{ID: 7, Username: "boss", FirstName: "Boss"}))
|
||||
|
||||
got := rb.LastSent().Text()
|
||||
if !strings.Contains(got, "Acme Corp") {
|
||||
t.Errorf("reply missing custom arg Acme Corp: %q", got)
|
||||
}
|
||||
if strings.Contains(got, "VNG") {
|
||||
t.Errorf("reply unexpectedly contains default VNG: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrongTruongHop_HTMLEscapesArg(t *testing.T) {
|
||||
rb, _ := installMisc(t, 999)
|
||||
rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop <script>",
|
||||
&models.User{ID: 7, Username: "boss", FirstName: "Boss"}))
|
||||
|
||||
got := rb.LastSent().Text()
|
||||
if !strings.Contains(got, "<script>") {
|
||||
t.Errorf("reply did not HTML-escape arg: %q", got)
|
||||
}
|
||||
if strings.Contains(got, "<script>") {
|
||||
t.Errorf("reply leaked raw <script>: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrongTruongHop_NoUsernameFallsBackToLink(t *testing.T) {
|
||||
rb, _ := installMisc(t, 999)
|
||||
rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop",
|
||||
&models.User{ID: 42, FirstName: "Anh"})) // no Username
|
||||
|
||||
got := rb.LastSent().Text()
|
||||
wantLink := `<a href="tg://user?id=42">Anh</a>`
|
||||
if n := strings.Count(got, wantLink); n != 2 {
|
||||
t.Errorf("reply contains link %q %d times, want 2: %q", wantLink, n, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrongTruongHop_EmptyDisplayNameFallsBackToThanhVien(t *testing.T) {
|
||||
rb, _ := installMisc(t, 999)
|
||||
rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop",
|
||||
&models.User{ID: 42})) // no Username, no FirstName/LastName
|
||||
|
||||
got := rb.LastSent().Text()
|
||||
wantLink := `<a href="tg://user?id=42">thành viên</a>`
|
||||
if n := strings.Count(got, wantLink); n != 2 {
|
||||
t.Errorf("reply contains fallback link %d times, want 2: %q", n, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFortytwo_OwnerOnly(t *testing.T) {
|
||||
rb, _ := installMisc(t, 999)
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-telegram/bot"
|
||||
@@ -21,6 +23,14 @@ import (
|
||||
// lastPingKey is the per-module KV key /ping writes and /mstats reads.
|
||||
const lastPingKey = "last_ping"
|
||||
|
||||
// defaultTarget is the substituted "investigator" name when /trongtruonghop is
|
||||
// invoked without an argument.
|
||||
const defaultTarget = "VNG"
|
||||
|
||||
// trongTruongHopTemplate is the disclaimer rendered by /trongtruonghop. Three
|
||||
// %s slots: target (escaped), sender mention, sender mention.
|
||||
const trongTruongHopTemplate = "Trong trường hợp nhóm này bị điều tra bởi %s, %s khẳng định không liên quan tới nhóm hoặc những cá nhân khác trong nhóm này. %s không rõ tại sao lại có mặt ở đây vào thời điểm này, có lẽ tài khoản đã được thêm bởi một bên thứ ba."
|
||||
|
||||
// lastPing mirrors the JS bot's wire format: { at: <ms-since-epoch number> }.
|
||||
// Stored as int64 ms-epoch (not time.Time → RFC3339) so a future cross-runtime
|
||||
// KV export/import migration round-trips byte-for-byte.
|
||||
@@ -36,6 +46,7 @@ func New(deps modules.Deps) modules.Module {
|
||||
pingCommand(deps),
|
||||
mstatsCommand(deps),
|
||||
fortytwoCommand(),
|
||||
trongTruongHopCommand(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -87,6 +98,45 @@ func mstatsCommand(deps modules.Deps) modules.Command {
|
||||
}
|
||||
}
|
||||
|
||||
// senderMention renders the mention used inside the trongtruonghop template.
|
||||
// Prefer @username (Telegram resolves it server-side and enforces a safe
|
||||
// charset). Fall back to a tg://user?id link with the user's display name when
|
||||
// the account has no username; escape the name because first/last names can
|
||||
// legitimately contain '<' or '&'.
|
||||
func senderMention(u *models.User) string {
|
||||
if u == nil {
|
||||
return "thành viên"
|
||||
}
|
||||
if u.Username != "" {
|
||||
return "@" + u.Username
|
||||
}
|
||||
name := strings.TrimSpace(u.FirstName + " " + u.LastName)
|
||||
if name == "" {
|
||||
name = "thành viên"
|
||||
}
|
||||
return fmt.Sprintf(`<a href="tg://user?id=%d">%s</a>`, u.ID, html.EscapeString(name))
|
||||
}
|
||||
|
||||
func trongTruongHopCommand() modules.Command {
|
||||
return modules.Command{
|
||||
Name: "trongtruonghop",
|
||||
Visibility: modules.VisibilityPublic,
|
||||
Description: "Phát biểu disclaimer cho thành viên hiện tại",
|
||||
Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error {
|
||||
if update.Message == nil || update.Message.From == nil {
|
||||
return nil
|
||||
}
|
||||
arg := strings.TrimSpace(chathelper.ArgAfterCommand(update.Message.Text))
|
||||
if arg == "" {
|
||||
arg = defaultTarget
|
||||
}
|
||||
mention := senderMention(update.Message.From)
|
||||
text := fmt.Sprintf(trongTruongHopTemplate, html.EscapeString(arg), mention, mention)
|
||||
return chathelper.ReplyHTML(ctx, b, update.Message, text)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func fortytwoCommand() modules.Command {
|
||||
return modules.Command{
|
||||
Name: "fortytwo",
|
||||
|
||||
@@ -18,9 +18,10 @@ func TestNew_RegistersExpectedCommands(t *testing.T) {
|
||||
mod := New(deps)
|
||||
|
||||
want := map[string]modules.Visibility{
|
||||
"ping": modules.VisibilityPublic,
|
||||
"mstats": modules.VisibilityProtected,
|
||||
"fortytwo": modules.VisibilityPrivate,
|
||||
"ping": modules.VisibilityPublic,
|
||||
"mstats": modules.VisibilityProtected,
|
||||
"fortytwo": modules.VisibilityPrivate,
|
||||
"trongtruonghop": modules.VisibilityPublic,
|
||||
}
|
||||
if len(mod.Commands) != len(want) {
|
||||
t.Fatalf("commands count = %d, want %d", len(mod.Commands), len(want))
|
||||
|
||||
Reference in new issue
Block a user