feat: enable Telegram features on binding
This commit is contained in:
@@ -170,6 +170,8 @@ func (a *App) telegramSettings() TelegramSettings {
|
||||
t.Configured = token != ""
|
||||
}
|
||||
}
|
||||
t.Enabled = t.ChatID != 0
|
||||
t.MenuEnabled = t.ChatID != 0
|
||||
return t
|
||||
}
|
||||
func (a *App) telegramSecret() (TelegramSettings, error) {
|
||||
@@ -204,6 +206,8 @@ func (a *App) telegramAPI(w http.ResponseWriter, r *http.Request) {
|
||||
jsonOut(w, 400, map[string]string{"error": "Bot Token 必填"})
|
||||
return
|
||||
}
|
||||
in.Enabled = in.ChatID != 0
|
||||
in.MenuEnabled = in.ChatID != 0
|
||||
var me struct {
|
||||
OK bool `json:"ok"`
|
||||
Result struct {
|
||||
@@ -309,7 +313,33 @@ func tgSend(t TelegramSettings, text string) error {
|
||||
}
|
||||
return tgCall(t.Token, "sendMessage", params, &out)
|
||||
}
|
||||
func (a *App) syncLegacyTelegramMenu() {
|
||||
a.telegramMu.Lock()
|
||||
defer a.telegramMu.Unlock()
|
||||
var t TelegramSettings
|
||||
if !a.store.GetJSON("telegram", &t) || t.ChatID == 0 || t.MenuEnabled {
|
||||
return
|
||||
}
|
||||
enc, ok := a.store.Get("telegram_token")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
token, err := a.vault.Decrypt(enc)
|
||||
if err != nil || token == "" {
|
||||
return
|
||||
}
|
||||
t.Token = token
|
||||
t.Configured = true
|
||||
t.Enabled = true
|
||||
t.MenuEnabled = true
|
||||
if tgSend(t, "✅ <b>Codex 查询菜单已自动启用</b>\n\n现在可以使用菜单查询额度信息。") != nil {
|
||||
return
|
||||
}
|
||||
t.Token = ""
|
||||
_ = a.store.SetJSON("telegram", t)
|
||||
}
|
||||
func (a *App) telegramLoop() {
|
||||
a.syncLegacyTelegramMenu()
|
||||
for {
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
@@ -367,16 +397,14 @@ func (a *App) handleTG(t TelegramSettings, chat int64, text string) {
|
||||
}
|
||||
if a.store.GetJSON("telegram_bind", &b) && b.Expires > time.Now().Unix() && strings.TrimSpace(strings.TrimPrefix(text, "/bind ")) == b.Code {
|
||||
t.ChatID = chat
|
||||
t.Enabled = true
|
||||
t.MenuEnabled = true
|
||||
safe := t
|
||||
safe.Token = ""
|
||||
_ = a.store.SetJSON("telegram", safe)
|
||||
_ = a.store.Set("telegram_bind", "{}")
|
||||
t.ChatID = chat
|
||||
message := "✅ <b>绑定成功</b>\n\nCodex 额度提醒将发送到此会话。"
|
||||
if t.MenuEnabled {
|
||||
message = "✅ <b>绑定成功</b>\n\n现在可以使用菜单查询 Codex 额度信息。"
|
||||
}
|
||||
_ = tgSend(t, message)
|
||||
_ = tgSend(t, "✅ <b>绑定成功</b>\n\n额度提醒和 Codex 额度查询菜单已启用。")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ func TestTelegramDeleteWaitsForInFlightSaveAndRemainsFinal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisabledTelegramSkipsAutomaticReminderButAllowsManualTest(t *testing.T) {
|
||||
func TestBoundTelegramIgnoresLegacyDisabledFlags(t *testing.T) {
|
||||
a := newReminderTestApp(t)
|
||||
enc, err := a.vault.Encrypt("secret-token")
|
||||
if err != nil {
|
||||
@@ -212,14 +212,144 @@ func TestDisabledTelegramSkipsAutomaticReminderButAllowsManualTest(t *testing.T)
|
||||
return nil
|
||||
}
|
||||
a.sendPendingReminders(now)
|
||||
if calls != 0 {
|
||||
t.Fatalf("automatic Telegram calls = %d; want 0", calls)
|
||||
if calls != 1 {
|
||||
t.Fatalf("automatic Telegram calls = %d; want 1", calls)
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
a.telegramTest(recorder, httptest.NewRequest(http.MethodPost, "/api/v1/settings/telegram/test", nil))
|
||||
if recorder.Code != http.StatusOK || calls != 1 {
|
||||
if recorder.Code != http.StatusOK || calls != 2 {
|
||||
t.Fatalf("manual test status = %d calls = %d body = %s", recorder.Code, calls, recorder.Body.String())
|
||||
}
|
||||
settings := a.telegramSettings()
|
||||
if !settings.Enabled || !settings.MenuEnabled {
|
||||
t.Fatalf("effective settings = %#v", settings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyBoundTelegramRestoresMenuOnce(t *testing.T) {
|
||||
a := newReminderTestApp(t)
|
||||
enc, err := a.vault.Encrypt("secret-token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.Set("telegram_token", enc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.SetJSON("telegram", TelegramSettings{ChatID: 123, Enabled: false, MenuEnabled: false, Configured: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original := tgCall
|
||||
t.Cleanup(func() { tgCall = original })
|
||||
calls := 0
|
||||
var sent map[string]any
|
||||
tgCall = func(_ string, method string, params any, _ any) error {
|
||||
if method != "sendMessage" {
|
||||
t.Fatalf("method = %q", method)
|
||||
}
|
||||
calls++
|
||||
body, marshalErr := json.Marshal(params)
|
||||
if marshalErr != nil {
|
||||
return marshalErr
|
||||
}
|
||||
return json.Unmarshal(body, &sent)
|
||||
}
|
||||
|
||||
a.syncLegacyTelegramMenu()
|
||||
a.syncLegacyTelegramMenu()
|
||||
if calls != 1 {
|
||||
t.Fatalf("menu restore calls = %d; want 1", calls)
|
||||
}
|
||||
replyMarkup, ok := sent["reply_markup"].(map[string]any)
|
||||
if !ok || replyMarkup["keyboard"] == nil {
|
||||
t.Fatalf("send params = %#v", sent)
|
||||
}
|
||||
var stored TelegramSettings
|
||||
if !a.store.GetJSON("telegram", &stored) || !stored.Enabled || !stored.MenuEnabled {
|
||||
t.Fatalf("stored settings = %#v", stored)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramSaveCannotDisableFeaturesForBoundChat(t *testing.T) {
|
||||
a := newReminderTestApp(t)
|
||||
original := tgCall
|
||||
t.Cleanup(func() { tgCall = original })
|
||||
tgCall = func(_ string, method string, _ any, out any) error {
|
||||
if method == "sendMessage" {
|
||||
return nil
|
||||
}
|
||||
if method == "getMe" {
|
||||
return json.Unmarshal([]byte(`{"ok":true,"result":{"first_name":"Test","username":"test_bot"}}`), out)
|
||||
}
|
||||
t.Fatalf("method = %q", method)
|
||||
return nil
|
||||
}
|
||||
|
||||
body := bytes.NewBufferString(`{"token":"token","chatId":123,"enabled":false,"menuEnabled":false}`)
|
||||
recorder := httptest.NewRecorder()
|
||||
a.telegramAPI(recorder, httptest.NewRequest(http.MethodPut, "/api/v1/settings/telegram", body))
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var response TelegramSettings
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !response.Enabled || !response.MenuEnabled {
|
||||
t.Fatalf("response = %#v", response)
|
||||
}
|
||||
var stored TelegramSettings
|
||||
if !a.store.GetJSON("telegram", &stored) || !stored.Enabled || !stored.MenuEnabled {
|
||||
t.Fatalf("stored settings = %#v", stored)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramBindEnablesFeaturesAndSendsMenu(t *testing.T) {
|
||||
a := newReminderTestApp(t)
|
||||
enc, err := a.vault.Encrypt("secret-token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.Set("telegram_token", enc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.SetJSON("telegram", TelegramSettings{Configured: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.SetJSON("telegram_bind", map[string]any{"code": "123456", "expires": time.Now().Add(time.Minute).Unix()}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original := tgCall
|
||||
t.Cleanup(func() { tgCall = original })
|
||||
var sent map[string]any
|
||||
tgCall = func(_ string, method string, params any, _ any) error {
|
||||
if method != "sendMessage" {
|
||||
t.Fatalf("method = %q", method)
|
||||
}
|
||||
body, marshalErr := json.Marshal(params)
|
||||
if marshalErr != nil {
|
||||
return marshalErr
|
||||
}
|
||||
return json.Unmarshal(body, &sent)
|
||||
}
|
||||
|
||||
a.handleTG(a.telegramSecretForTest(t), 456, "/bind 123456")
|
||||
settings := a.telegramSettings()
|
||||
if settings.ChatID != 456 || !settings.Enabled || !settings.MenuEnabled {
|
||||
t.Fatalf("settings = %#v", settings)
|
||||
}
|
||||
replyMarkup, ok := sent["reply_markup"].(map[string]any)
|
||||
if !ok || replyMarkup["keyboard"] == nil {
|
||||
t.Fatalf("send params = %#v", sent)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) telegramSecretForTest(t *testing.T) TelegramSettings {
|
||||
t.Helper()
|
||||
settings, err := a.telegramSecret()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
func reminderDashboard(fetchedAt int64, used float64, resetsAt int64) Dashboard {
|
||||
|
||||
Reference in New Issue
Block a user