feat(misc): default /gacha options to 5 stars with a 3*/4* prefix

Rarity is now a leading prefix such as "4* Pho" or "3* Rice"; options
without one are 5 stars. This replaces the trailing "*5" tag and its
3-star default.
This commit is contained in:
tiennm99 committed 2026-10-01 13:59:12 +07:00
1 parent 004325d947
commit 5561c33068
4 files changed
+23 -20

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ Atlas via long polling and an in-process cron scheduler.
| Module | What it does |
|---|---|
| `util` | `/help`, `/info` (admin), `/stickerid` (owner) |
| `misc` | `/ping`, `/ping_stats` (admin), `/random`, `/wheelofnames`, `/gacha` (Genshin-style wish; tag `*4`/`*5` for rarity), `/ff` (admin), `/xlt1`, `/giaxang` (Petrolimex retail fuel prices), `/the_answer` (owner), `/trongtruonghop` + `/tth`, `/trongtruonghopvng` + `/tthvng` disclaimers |
| `misc` | `/ping`, `/ping_stats` (admin), `/random`, `/wheelofnames`, `/gacha` (Genshin-style wish; options are 5★ by default, prefix `4*` or `3*`), `/ff` (admin), `/xlt1`, `/giaxang` (Petrolimex retail fuel prices), `/the_answer` (owner), `/trongtruonghop` + `/tth`, `/trongtruonghopvng` + `/tthvng` disclaimers |
| `amlich` | Vietnamese lunar calendar: `/amlich` (dương lịch → âm lịch, defaults to today), `/duonglich` (âm lịch → dương lịch, `nhuan` flag for leap months); dates accept `d`, `d/m`, or `d/m/yyyy` — missing parts fill from today in the input's calendar. Years 1800–2199 only |
| `wordle` | Daily Wordle game: `/wordle [word]`, `/wordle_new`, `/wordle_giveup`, `/wordle_stats` |
| `loldle` | League-of-Legends "guess the champion": `/loldle [champion]`, `/loldle_giveup`, `/loldle_stats`, `/loldle_setmax` (owner) |
+3 -2
View File
@@ -89,8 +89,9 @@ reply is immediate.
segment, so `.../api/gif` becomes `.../api/gacha`. It renders a 7-second
`640x360` silent MP4 wish animation, posts `Wishing...` while it renders, and
falls back to a text reply such as `★★★★★ Pizza` on the same failures. Every
option is equally likely, as with `/random`; a `*4` or `*5` tag only sets the
rarity shown in the animation and reply. Untagged options are 3★.
option is equally likely, as with `/random`; the rarity only sets what the
animation and reply show. Options are 5★ by default; prefix `4*` or `3*` to
lower one, e.g. `/gacha Pizza, 4* Pho, 3* Rice`.
## 1. MongoDB Atlas (M0)
+8 -8
View File
@@ -17,17 +17,17 @@ import (
)
const (
gachaUsage = "Usage: /gacha <option,...>\nTag rarity with *4 or *5, e.g. /gacha Pizza*5, Pho*4, Rice"
gachaUsage = "Usage: /gacha <option,...>\nOptions are 5* by default; prefix 4* or 3* to lower one, e.g. /gacha Pizza, 4* Pho, 3* Rice"
gachaPlaceholder = "Wishing..."
gachaFilename = "gacha.mp4"
gachaMinRarity = 3
gachaMaxRarity = 5
gachaDefaultRarity = gachaMinRarity
gachaDefaultRarity = gachaMaxRarity
)
// gachaRarityTag matches a trailing "*3", "*4", or "*5" rarity tag.
var gachaRarityTag = regexp.MustCompile(`\s*\*\s*([345])$`)
// gachaRarityTag matches a leading "3*", "4*", or "5*" rarity prefix.
var gachaRarityTag = regexp.MustCompile(`^([345])\s*\*\s*`)
type gachaOption struct {
Label string
@@ -35,15 +35,15 @@ type gachaOption struct {
}
// parseGachaOptions splits comma-separated options and strips each one's
// rarity tag. Untagged options are 3★; options left blank after the tag is
// removed are dropped.
// rarity prefix. Unprefixed options are 5★; options left blank after the
// prefix is removed are dropped.
func parseGachaOptions(arg string) []gachaOption {
parts := splitWheelOptions(arg)
out := make([]gachaOption, 0, len(parts))
for _, part := range parts {
option := gachaOption{Label: part, Rarity: gachaDefaultRarity}
if m := gachaRarityTag.FindStringSubmatchIndex(part); m != nil {
option.Label = strings.TrimSpace(part[:m[0]])
option.Label = strings.TrimSpace(part[m[1]:])
option.Rarity = int(part[m[2]] - '0')
}
if option.Label != "" {
@@ -62,7 +62,7 @@ func gachaCommand() modules.Command {
return modules.Command{
Name: "gacha",
Visibility: modules.VisibilityPublic,
Description: "Wish for one comma-separated option, Genshin style; tag *4 or *5 for rarity",
Description: "Wish for one comma-separated option, Genshin style; 5* by default, prefix 4* or 3*",
Parameters: "<option,...>",
Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error {
if update.Message == nil {
+11 -9
View File
@@ -14,14 +14,16 @@ import (
var mp4Bytes = []byte("\x00\x00\x00\x18ftypisom-remote")
func TestParseGachaOptions(t *testing.T) {
got := parseGachaOptions(" Pizza*5, Pho *4 , Rice, Bun * 3, *5, , Cake*7, a*b ")
got := parseGachaOptions(" Pizza, 4* Pho , 3*Rice, 3 * Bun, 5* Cake, 5*, , 7* Tea, a*b, Pho 4* ")
want := []gachaOption{
{Label: "Pizza", Rarity: 5},
{Label: "Pho", Rarity: 4},
{Label: "Rice", Rarity: 3},
{Label: "Bun", Rarity: 3},
{Label: "Cake*7", Rarity: 3},
{Label: "a*b", Rarity: 3},
{Label: "Cake", Rarity: 5},
{Label: "7* Tea", Rarity: 5},
{Label: "a*b", Rarity: 5},
{Label: "Pho 4*", Rarity: 5},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("parseGachaOptions() = %#v, want %#v", got, want)
@@ -29,7 +31,7 @@ func TestParseGachaOptions(t *testing.T) {
}
func TestGacha_EmptyArgsRepliesUsage(t *testing.T) {
for _, text := range []string{"/gacha", "/gacha , ,", "/gacha *5"} {
for _, text := range []string{"/gacha", "/gacha , ,", "/gacha 5*"} {
t.Run(text, func(t *testing.T) {
rb, _ := installMisc(t, 999)
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, text))
@@ -43,7 +45,7 @@ func TestGacha_EmptyArgsRepliesUsage(t *testing.T) {
func TestGacha_NotConfiguredRepliesWithStars(t *testing.T) {
rb, _ := installMisc(t, 999)
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha Pizza*5"))
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha Pizza"))
calls := rb.Sent()
if len(calls) != 1 || calls[0].Method != "sendMessage" || calls[0].Text() != "★★★★★ Pizza" {
@@ -68,7 +70,7 @@ func TestGacha_UsesRemoteAPIWhenConfigured(t *testing.T) {
t.Setenv(wheelOfNamesAPITokenEnv, "remote-token")
rb, _ := installMisc(t, 999)
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha Pho*4"))
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha 4* Pho"))
if gotPath != "/api/gacha" {
t.Fatalf("path = %q, want /api/gacha", gotPath)
@@ -113,7 +115,7 @@ func TestGacha_RemoteFailureFallsBackToText(t *testing.T) {
t.Setenv(wheelOfNamesAPIURLEnv, server.URL+"/api/gif")
rb, _ := installMisc(t, 999)
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha Rice"))
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha 3* Rice"))
sent := rb.Sent()
if len(sent) != 2 {
@@ -134,7 +136,7 @@ func TestGacha_SendAnimationFailureFallsBackToText(t *testing.T) {
rb, _ := installMisc(t, 999)
rb.FailMethod("sendAnimation", http.StatusInternalServerError, "")
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha Rice"))
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/gacha 3* Rice"))
call := rb.LastSent()
if call.Method != "editMessageText" || call.Text() != "★★★ Rice" {
@@ -151,7 +153,7 @@ func TestGacha_ForwardsMessageThreadID(t *testing.T) {
t.Setenv(wheelOfNamesAPIURLEnv, server.URL+"/api/gif")
rb, _ := installMisc(t, 999)
update := testutil.NewSupergroupMessage(-100, 7, "/gacha Rice")
update := testutil.NewSupergroupMessage(-100, 7, "/gacha 3* Rice")
update.Message.MessageThreadID = 42
rb.Bot.ProcessUpdate(context.Background(), update)