diff --git a/README.md b/README.md
index 6dedf47..a7a3cdb 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Atlas via long polling and an in-process cron scheduler.
| Module | What it does |
|---|---|
| `util` | `/help`, `/info`, `/stickerid` |
-| `misc` | `/ping`, `/ping_stats`, `/random`, `/wheelofnames`, `/wheelofnamesbeta`, `/the_answer`, `/trongtruonghop` + `/tth` disclaimer |
+| `misc` | `/ping`, `/ping_stats`, `/random`, `/wheelofnames`, `/wheelofnamesbeta`, `/the_answer`, `/trongtruonghop` + `/tth`, `/trongtruonghopvng` + `/tthvng` disclaimers |
| `wordle` | Daily Wordle game |
| `loldle` | League-of-Legends "guess the champion" |
| `lol` | Pro-match schedule (`/lol`, `/lol_tomorrow`, `/lol_this_week`, `/lol_next_week`) + daily push |
diff --git a/internal/modules/misc/handlers_test.go b/internal/modules/misc/handlers_test.go
index 74d6b99..869980f 100644
--- a/internal/modules/misc/handlers_test.go
+++ b/internal/modules/misc/handlers_test.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "fmt"
"image"
"image/gif"
"math"
@@ -638,17 +639,15 @@ func trongTruongHopUpdate(t *testing.T, text string, from *models.User) *models.
return u
}
-func TestTrongTruongHop_DefaultArgUsesVNG(t *testing.T) {
+func TestTrongTruongHop_DefaultText(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)
+ want := fmt.Sprintf(trongTruongHopTemplate, defaultTarget, "@boss", "@boss")
+ if got != want {
+ t.Errorf("reply = %q, want %q", got, want)
}
}
@@ -661,8 +660,11 @@ func TestTrongTruongHop_CustomArg(t *testing.T) {
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)
+ if strings.Contains(got, defaultTarget) {
+ t.Errorf("reply unexpectedly contains default target: %q", got)
+ }
+ if n := strings.Count(got, "@boss"); n != 2 {
+ t.Errorf("reply mentions @boss %d times, want 2: %q", n, got)
}
}
@@ -694,27 +696,51 @@ func TestTrongTruongHop_HTMLEscapesArg(t *testing.T) {
}
}
-func TestTrongTruongHop_NoUsernameFallsBackToLink(t *testing.T) {
+func TestTrongTruongHopVNG_DefaultText(t *testing.T) {
rb, _ := installMisc(t, 999)
- rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop",
- &models.User{ID: 42, FirstName: "Anh"})) // no Username
+ rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghopvng ignored arg",
+ &models.User{ID: 7, Username: "boss", FirstName: "Boss"}))
got := rb.LastSent().Text()
- wantLink := `Anh`
- if n := strings.Count(got, wantLink); n != 2 {
- t.Errorf("reply contains link %q %d times, want 2: %q", wantLink, n, got)
+ want := fmt.Sprintf(trongTruongHopVNGTemplate, "@boss", "@boss")
+ if got != want {
+ t.Errorf("reply = %q, want %q", got, want)
}
}
-func TestTrongTruongHop_EmptyDisplayNameFallsBackToThanhVien(t *testing.T) {
+func TestTTHVNGAlias_DefaultText(t *testing.T) {
rb, _ := installMisc(t, 999)
- rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop",
- &models.User{ID: 42})) // no Username, no FirstName/LastName
+ rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/tthvng ignored arg",
+ &models.User{ID: 7, Username: "boss", FirstName: "Boss"}))
got := rb.LastSent().Text()
- wantLink := `thành viên`
+ want := fmt.Sprintf(trongTruongHopVNGTemplate, "@boss", "@boss")
+ if got != want {
+ t.Errorf("reply = %q, want %q", got, want)
+ }
+}
+
+func TestTrongTruongHop_NoUsernameFallsBackToDisplayNameMention(t *testing.T) {
+ rb, _ := installMisc(t, 999)
+ rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghop",
+ &models.User{ID: 42, FirstName: "Anh", LastName: "Le"}))
+
+ got := rb.LastSent().Text()
+ wantLink := `Anh Le`
if n := strings.Count(got, wantLink); n != 2 {
- t.Errorf("reply contains fallback link %d times, want 2: %q", n, got)
+ t.Errorf("reply contains display-name mention %q %d times, want 2: %q", wantLink, n, got)
+ }
+}
+
+func TestTrongTruongHopVNG_NoUsernameFallsBackToDisplayNameMention(t *testing.T) {
+ rb, _ := installMisc(t, 999)
+ rb.Bot.ProcessUpdate(context.Background(), trongTruongHopUpdate(t, "/trongtruonghopvng ignored arg",
+ &models.User{ID: 42, FirstName: "Anh", LastName: "Le"}))
+
+ got := rb.LastSent().Text()
+ wantLink := `Anh Le`
+ if n := strings.Count(got, wantLink); n != 2 {
+ t.Errorf("reply contains display-name mention %q %d times, want 2: %q", wantLink, n, got)
}
}
diff --git a/internal/modules/misc/misc.go b/internal/modules/misc/misc.go
index 569fedb..d8d053f 100644
--- a/internal/modules/misc/misc.go
+++ b/internal/modules/misc/misc.go
@@ -2,7 +2,7 @@
// /ping (public, exercises KV write), /ping_stats (protected, exercises KV
// read), /random (public random picker), /wheelofnames (public streaming
// random picker), /wheelofnamesbeta (public GIF wheel picker), /the_answer
-// (private easter egg).
+// (private easter egg), and small public disclaimer commands.
package misc
import (
@@ -26,13 +26,17 @@ import (
const lastPingKey = "last_ping"
// defaultTarget is the substituted "investigator" name when /trongtruonghop is
-// invoked without an argument.
-const defaultTarget = "VNG"
+// invoked without an argument. The command keeps a custom-arg override.
+const defaultTarget = "các cơ quan trực thuộc Bộ CA hoặc các tổ chức chính trị tương tự phục vụ cho nhà nước CHXHCNVN"
// 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."
+// trongTruongHopVNGTemplate is the fixed-target disclaimer rendered by
+// /trongtruonghopvng. Two %s slots: sender mention, sender mention.
+const trongTruongHopVNGTemplate = "Trong trường hợp nhóm này bị điều tra bởi công ty cổ phần tập đoàn VNG nói chung và công ty 2morebits nói riêng, %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 is the value stored at the `last_ping` key: { at: }.
// int64 ms-epoch (not time.Time → RFC3339) keeps the on-disk shape compact
// and consistent with every other timestamp field in the bot's KV.
@@ -54,6 +58,8 @@ func New(deps modules.Deps) modules.Module {
theAnswerCommand(),
trongTruongHopCommand("trongtruonghop"),
trongTruongHopCommand("tth"),
+ trongTruongHopVNGCommand("trongtruonghopvng"),
+ trongTruongHopVNGCommand("tthvng"),
},
}
}
@@ -104,11 +110,9 @@ func pingStatsCommand(store storage.DocStore[lastPing]) 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 '&'.
+// senderMention renders the mention used inside disclaimer templates. Prefer
+// @username; when absent, link the sender's display name so Telegram still
+// mentions the account.
func senderMention(u *models.User) string {
if u == nil {
return "thành viên"
@@ -127,9 +131,9 @@ func trongTruongHopCommand(name string) modules.Command {
return modules.Command{
Name: name,
Visibility: modules.VisibilityPublic,
- Description: "Phát biểu disclaimer cho thành viên hiện tại",
+ Description: "Phát biểu disclaimer mặc định",
Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error {
- if update.Message == nil || update.Message.From == nil {
+ if update.Message == nil {
return nil
}
arg := strings.TrimSpace(chathelper.ArgAfterCommand(update.Message.Text))
@@ -143,6 +147,22 @@ func trongTruongHopCommand(name string) modules.Command {
}
}
+func trongTruongHopVNGCommand(name string) modules.Command {
+ return modules.Command{
+ Name: name,
+ Visibility: modules.VisibilityPublic,
+ Description: "Phát biểu disclaimer VNG mặc định",
+ Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error {
+ if update.Message == nil {
+ return nil
+ }
+ mention := senderMention(update.Message.From)
+ text := fmt.Sprintf(trongTruongHopVNGTemplate, mention, mention)
+ return chathelper.ReplyHTML(ctx, b, update.Message, text)
+ },
+ }
+}
+
func theAnswerCommand() modules.Command {
return modules.Command{
Name: "the_answer",
diff --git a/internal/modules/misc/misc_test.go b/internal/modules/misc/misc_test.go
index 6af94bd..5f8e415 100644
--- a/internal/modules/misc/misc_test.go
+++ b/internal/modules/misc/misc_test.go
@@ -21,14 +21,16 @@ func TestNew_RegistersExpectedCommands(t *testing.T) {
mod := New(deps)
want := map[string]modules.Visibility{
- "ping": modules.VisibilityPublic,
- "ping_stats": modules.VisibilityProtected,
- "random": modules.VisibilityPublic,
- "wheelofnames": modules.VisibilityPublic,
- "wheelofnamesbeta": modules.VisibilityPublic,
- "the_answer": modules.VisibilityPrivate,
- "trongtruonghop": modules.VisibilityPublic,
- "tth": modules.VisibilityPublic,
+ "ping": modules.VisibilityPublic,
+ "ping_stats": modules.VisibilityProtected,
+ "random": modules.VisibilityPublic,
+ "wheelofnames": modules.VisibilityPublic,
+ "wheelofnamesbeta": modules.VisibilityPublic,
+ "the_answer": modules.VisibilityPrivate,
+ "trongtruonghop": modules.VisibilityPublic,
+ "tth": modules.VisibilityPublic,
+ "trongtruonghopvng": modules.VisibilityPublic,
+ "tthvng": modules.VisibilityPublic,
}
if len(mod.Commands) != len(want) {
t.Fatalf("commands count = %d, want %d", len(mod.Commands), len(want))
diff --git a/telegram-commands.json b/telegram-commands.json
index 7be1344..83a2d5a 100644
--- a/telegram-commands.json
+++ b/telegram-commands.json
@@ -22,12 +22,20 @@
},
{
"command": "trongtruonghop",
- "description": "Phát biểu disclaimer cho thành viên hiện tại"
+ "description": "Phát biểu disclaimer mặc định"
},
{
"command": "tth",
"description": "Alias for /trongtruonghop"
},
+ {
+ "command": "trongtruonghopvng",
+ "description": "Phát biểu disclaimer VNG mặc định"
+ },
+ {
+ "command": "tthvng",
+ "description": "Alias for /trongtruonghopvng"
+ },
{
"command": "wordle",
"description": "Classic wordle; guess the 5-letter word"