mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
fix: simplify telegram channel access rules
This commit is contained in:
@@ -238,7 +238,7 @@ mkdir -p data cache media downloads
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
# 固定版本;需要升级时改成新的 MediaStationGo-vX.Y.Z 后执行 docker compose pull && docker compose up -d
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.23
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
||||
MEDIASTATION_HTTP_PORT=18080
|
||||
|
||||
# 程序数据和缓存建议放在 MediaStationGo 部署目录下,便于备份和迁移。
|
||||
@@ -307,7 +307,7 @@ vim docker-compose.yml
|
||||
#
|
||||
# 镜像版本:
|
||||
# 默认拉取 latest;如需固定版本,创建 .env 并写入:
|
||||
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.23
|
||||
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
||||
#
|
||||
# 路径映射总览:
|
||||
# /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。
|
||||
@@ -516,7 +516,7 @@ docker compose up -d
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.23
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
||||
MEDIASTATION_HTTP_PORT=18080
|
||||
MEDIASTATION_DATA_DIR=./data
|
||||
MEDIASTATION_CACHE_DIR=./cache
|
||||
@@ -779,26 +779,26 @@ cd MediaStationGo
|
||||
|
||||
| 平台 | 包名示例 |
|
||||
| --- | --- |
|
||||
| Linux x86_64 | `MediaStationGo-v0.0.23-linux-amd64.tar.gz` |
|
||||
| Linux ARM64 | `MediaStationGo-v0.0.23-linux-arm64.tar.gz` |
|
||||
| Windows x86_64 | `MediaStationGo-v0.0.23-windows-amd64.zip` |
|
||||
| macOS Intel | `MediaStationGo-v0.0.23-darwin-amd64.tar.gz` |
|
||||
| macOS Apple Silicon | `MediaStationGo-v0.0.23-darwin-arm64.tar.gz` |
|
||||
| Linux x86_64 | `MediaStationGo-v0.0.24-linux-amd64.tar.gz` |
|
||||
| Linux ARM64 | `MediaStationGo-v0.0.24-linux-arm64.tar.gz` |
|
||||
| Windows x86_64 | `MediaStationGo-v0.0.24-windows-amd64.zip` |
|
||||
| macOS Intel | `MediaStationGo-v0.0.24-darwin-amd64.tar.gz` |
|
||||
| macOS Apple Silicon | `MediaStationGo-v0.0.24-darwin-arm64.tar.gz` |
|
||||
|
||||
部署步骤:
|
||||
|
||||
```bash
|
||||
# Linux 示例
|
||||
tar -xzf MediaStationGo-v0.0.23-linux-amd64.tar.gz
|
||||
cd MediaStationGo-v0.0.23-linux-amd64
|
||||
tar -xzf MediaStationGo-v0.0.24-linux-amd64.tar.gz
|
||||
cd MediaStationGo-v0.0.24-linux-amd64
|
||||
MEDIASTATION_APP_PORT=18080 ./mediastation-go
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
```powershell
|
||||
Expand-Archive .\MediaStationGo-v0.0.23-windows-amd64.zip
|
||||
cd .\MediaStationGo-v0.0.23-windows-amd64
|
||||
Expand-Archive .\MediaStationGo-v0.0.24-windows-amd64.zip
|
||||
cd .\MediaStationGo-v0.0.24-windows-amd64
|
||||
$env:MEDIASTATION_APP_PORT = "18080"
|
||||
.\mediastation-go.exe
|
||||
```
|
||||
|
||||
+11
-11
@@ -235,7 +235,7 @@ mkdir -p data cache media downloads
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.23
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
||||
MEDIASTATION_HTTP_PORT=18080
|
||||
MEDIASTATION_DATA_DIR=./data
|
||||
MEDIASTATION_CACHE_DIR=./cache
|
||||
@@ -344,7 +344,7 @@ For production, pin a specific release tag instead of using `latest`. Recommende
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.23
|
||||
MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
||||
MEDIASTATION_HTTP_PORT=18080
|
||||
MEDIASTATION_DATA_DIR=./data
|
||||
MEDIASTATION_CACHE_DIR=./cache
|
||||
@@ -593,25 +593,25 @@ Each release provides multi-platform archives:
|
||||
|
||||
| Platform | Package example |
|
||||
| --- | --- |
|
||||
| Linux x86_64 | `MediaStationGo-v0.0.23-linux-amd64.tar.gz` |
|
||||
| Linux ARM64 | `MediaStationGo-v0.0.23-linux-arm64.tar.gz` |
|
||||
| Windows x86_64 | `MediaStationGo-v0.0.23-windows-amd64.zip` |
|
||||
| macOS Intel | `MediaStationGo-v0.0.23-darwin-amd64.tar.gz` |
|
||||
| macOS Apple Silicon | `MediaStationGo-v0.0.23-darwin-arm64.tar.gz` |
|
||||
| Linux x86_64 | `MediaStationGo-v0.0.24-linux-amd64.tar.gz` |
|
||||
| Linux ARM64 | `MediaStationGo-v0.0.24-linux-arm64.tar.gz` |
|
||||
| Windows x86_64 | `MediaStationGo-v0.0.24-windows-amd64.zip` |
|
||||
| macOS Intel | `MediaStationGo-v0.0.24-darwin-amd64.tar.gz` |
|
||||
| macOS Apple Silicon | `MediaStationGo-v0.0.24-darwin-arm64.tar.gz` |
|
||||
|
||||
Linux example:
|
||||
|
||||
```bash
|
||||
tar -xzf MediaStationGo-v0.0.23-linux-amd64.tar.gz
|
||||
cd MediaStationGo-v0.0.23-linux-amd64
|
||||
tar -xzf MediaStationGo-v0.0.24-linux-amd64.tar.gz
|
||||
cd MediaStationGo-v0.0.24-linux-amd64
|
||||
MEDIASTATION_APP_PORT=18080 ./mediastation-go
|
||||
```
|
||||
|
||||
Windows example:
|
||||
|
||||
```powershell
|
||||
Expand-Archive .\MediaStationGo-v0.0.23-windows-amd64.zip
|
||||
cd .\MediaStationGo-v0.0.23-windows-amd64
|
||||
Expand-Archive .\MediaStationGo-v0.0.24-windows-amd64.zip
|
||||
cd .\MediaStationGo-v0.0.24-windows-amd64
|
||||
$env:MEDIASTATION_APP_PORT = "18080"
|
||||
.\mediastation-go.exe
|
||||
```
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
#
|
||||
# 镜像版本:
|
||||
# 默认拉取 latest;如需固定版本,创建 .env 并写入:
|
||||
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.23
|
||||
# MEDIASTATION_IMAGE_TAG=MediaStationGo-v0.0.24
|
||||
#
|
||||
# 路径映射总览:
|
||||
# /data 程序数据目录。保存 SQLite 数据库、JWT secret、系统配置等,必须持久化。
|
||||
|
||||
@@ -222,16 +222,22 @@ func (s *NotifyChannelService) dispatchOne(ctx context.Context, n model.NotifyCh
|
||||
case "telegram":
|
||||
telegramCfg := telegramStringConfigFromAny(cfg)
|
||||
token := telegramCfg["bot_token"]
|
||||
chat := telegramCfg["chat_id"]
|
||||
if token == "" || chat == "" {
|
||||
return errors.New("telegram missing bot_token / chat_id")
|
||||
chats := telegramTargetChatIDs(telegramCfg)
|
||||
if token == "" || len(chats) == 0 {
|
||||
return errors.New("telegram missing bot_token / group_chat_id / channel_chat_id")
|
||||
}
|
||||
text := fmt.Sprintf("<b>%s</b>\n\n%s", escapeHTML(title), escapeHTML(body))
|
||||
form := url.Values{}
|
||||
form.Set("chat_id", chat)
|
||||
form.Set("text", text)
|
||||
form.Set("parse_mode", "HTML")
|
||||
return telegramPostForm(ctx, telegramCfg, "sendMessage", form, 15*time.Second)
|
||||
var firstErr error
|
||||
for _, chat := range chats {
|
||||
form := url.Values{}
|
||||
form.Set("chat_id", chat)
|
||||
form.Set("text", text)
|
||||
form.Set("parse_mode", "HTML")
|
||||
if err := telegramPostForm(ctx, telegramCfg, "sendMessage", form, 15*time.Second); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
|
||||
case "bark":
|
||||
key := str(cfg["device_key"])
|
||||
@@ -325,15 +331,9 @@ func validateChannel(in ChannelInput) error {
|
||||
if str(cfg["bot_token"]) == "" {
|
||||
return errors.New("telegram bot_token required")
|
||||
}
|
||||
if str(cfg["chat_id"]) == "" {
|
||||
return errors.New("telegram notification chat_id required")
|
||||
}
|
||||
if str(cfg["admin_user_ids"]) == "" {
|
||||
return errors.New("telegram admin_user_ids required")
|
||||
}
|
||||
if str(cfg["group_chat_id"]) == "" && str(cfg["channel_chat_id"]) == "" && str(cfg["command_chat_id"]) == "" {
|
||||
return errors.New("telegram group_chat_id or channel_chat_id required: 请填写绑定群组 ID 或绑定频道 ID;如果通知 Chat ID 是群组/频道负数 ID,也可以直接填在 Chat ID")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -354,6 +354,48 @@ func normalizeChannelInput(in *ChannelInput) {
|
||||
}
|
||||
}
|
||||
|
||||
func telegramTargetChatIDs(cfg map[string]string) []string {
|
||||
seen := map[string]bool{}
|
||||
targets := []string{}
|
||||
for _, key := range []string{"group_chat_id", "channel_chat_id"} {
|
||||
chatID := strings.TrimSpace(cfg[key])
|
||||
if chatID == "" || seen[chatID] {
|
||||
continue
|
||||
}
|
||||
seen[chatID] = true
|
||||
targets = append(targets, chatID)
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
chatID := strings.TrimSpace(cfg["chat_id"])
|
||||
if strings.HasPrefix(chatID, "-") {
|
||||
targets = append(targets, chatID)
|
||||
}
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
for _, userID := range telegramConfiguredUserIDs(cfg["admin_user_ids"]) {
|
||||
if seen[userID] {
|
||||
continue
|
||||
}
|
||||
seen[userID] = true
|
||||
targets = append(targets, userID)
|
||||
}
|
||||
}
|
||||
return targets
|
||||
}
|
||||
|
||||
func telegramConfiguredUserIDs(raw string) []string {
|
||||
out := []string{}
|
||||
for _, value := range strings.FieldsFunc(raw, func(r rune) bool {
|
||||
return r == ',' || r == ';' || r == ',' || r == ' ' || r == '\n' || r == '\t'
|
||||
}) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value != "" {
|
||||
out = append(out, value)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// str safely extracts a string from an interface{} loaded from JSON.
|
||||
func str(v any) string {
|
||||
if v == nil {
|
||||
|
||||
@@ -14,24 +14,30 @@ type TelegramProvider struct{}
|
||||
// Send 发送 Telegram 消息。
|
||||
func (p *TelegramProvider) Send(ctx context.Context, cfg map[string]string, event NotifyEvent) error {
|
||||
botToken := cfg["bot_token"]
|
||||
chatID := cfg["chat_id"]
|
||||
chatIDs := telegramTargetChatIDs(cfg)
|
||||
parseMode := cfg["parse_mode"]
|
||||
if parseMode == "" {
|
||||
parseMode = "HTML"
|
||||
}
|
||||
|
||||
if botToken == "" || chatID == "" {
|
||||
return fmt.Errorf("telegram: bot_token and chat_id are required")
|
||||
if botToken == "" || len(chatIDs) == 0 {
|
||||
return fmt.Errorf("telegram: bot_token and group_chat_id/channel_chat_id are required")
|
||||
}
|
||||
|
||||
text := formatTelegramMessage(event, parseMode)
|
||||
|
||||
payload := map[string]string{
|
||||
"chat_id": chatID,
|
||||
"text": text,
|
||||
"parse_mode": parseMode,
|
||||
var firstErr error
|
||||
for _, chatID := range chatIDs {
|
||||
payload := map[string]string{
|
||||
"chat_id": chatID,
|
||||
"text": text,
|
||||
"parse_mode": parseMode,
|
||||
}
|
||||
if err := telegramPostJSON(ctx, cfg, "sendMessage", payload, 15*time.Second); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return telegramPostJSON(ctx, cfg, "sendMessage", payload, 15*time.Second)
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 Telegram 配置。
|
||||
@@ -39,8 +45,8 @@ func (p *TelegramProvider) ValidateConfig(cfg map[string]string) error {
|
||||
if cfg["bot_token"] == "" {
|
||||
return fmt.Errorf("telegram: bot_token is required")
|
||||
}
|
||||
if cfg["chat_id"] == "" {
|
||||
return fmt.Errorf("telegram: chat_id is required")
|
||||
if len(telegramTargetChatIDs(cfg)) == 0 {
|
||||
return fmt.Errorf("telegram: group_chat_id or channel_chat_id is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -32,3 +32,40 @@ func TestSanitizeTelegramErrorRedactsBotToken(t *testing.T) {
|
||||
t.Fatalf("expected timeout hint, got: %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTelegramChannelDoesNotRequireLegacyChatID(t *testing.T) {
|
||||
err := validateChannel(ChannelInput{
|
||||
Name: "Telegram",
|
||||
Type: "telegram",
|
||||
Config: map[string]any{
|
||||
"bot_token": "123456:ABC-def",
|
||||
"admin_user_ids": "10001",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("validateChannel returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramTargetChatIDsFallsBackToAdmins(t *testing.T) {
|
||||
got := telegramTargetChatIDs(map[string]string{
|
||||
"admin_user_ids": "10001, 10002",
|
||||
})
|
||||
if len(got) != 2 || got[0] != "10001" || got[1] != "10002" {
|
||||
t.Fatalf("got %#v, want admin user ids", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTelegramChannelMigratesLegacyChatID(t *testing.T) {
|
||||
input := ChannelInput{
|
||||
Name: "Telegram",
|
||||
Type: "telegram",
|
||||
Config: map[string]any{
|
||||
"chat_id": "-10001",
|
||||
},
|
||||
}
|
||||
normalizeChannelInput(&input)
|
||||
if got := str(input.Config["group_chat_id"]); got != "-10001" {
|
||||
t.Fatalf("group_chat_id = %q, want -10001", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ func (s *TelegramBotService) executeCommand(ctx context.Context, channel *model.
|
||||
cmd := strings.ToLower(parts[0])
|
||||
args := parts[1:]
|
||||
if msg.Chat.Type != "" && msg.Chat.Type != "private" && !s.telegramChatAllowed(channel, msg.Chat.ID) {
|
||||
return telegramCommandReply{Text: "此群组/频道未绑定到 Bot 管理入口,请在通知渠道里填写「命令群组/频道 Chat ID」。"}, nil
|
||||
return telegramCommandReply{Text: "此群组/频道未绑定到 Bot 管理入口,请在通知渠道里填写「绑定群组 ID」或「绑定频道 ID」。"}, nil
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
@@ -723,7 +723,7 @@ func (s *TelegramBotService) telegramUserCanBind(ctx context.Context, channel *m
|
||||
groupID := strings.TrimSpace(cfg["group_chat_id"])
|
||||
channelID := strings.TrimSpace(cfg["channel_chat_id"])
|
||||
if groupID == "" && channelID == "" {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
for _, chatID := range []string{groupID, channelID} {
|
||||
if chatID == "" {
|
||||
|
||||
@@ -183,7 +183,7 @@ function channelSummary(ch: NotifyChannel): string {
|
||||
const cfg = ch.config ?? {}
|
||||
switch (ch.type) {
|
||||
case 'telegram':
|
||||
return `Bot ${cfg.bot_token ? '已配置' : '未配置'} → 通知 ${cfg.chat_id ?? '-'} · 管理员 ${cfg.admin_user_ids ?? '-'} · 群组 ${cfg.group_chat_id ?? '-'} · 频道 ${cfg.channel_chat_id ?? '-'}`
|
||||
return `Bot ${cfg.bot_token ? '已配置' : '未配置'} → 管理员 ${cfg.admin_user_ids ?? '-'} · 群组 ${cfg.group_chat_id ?? '-'} · 频道 ${cfg.channel_chat_id ?? '-'}`
|
||||
case 'wechat':
|
||||
return `SendKey ${String(cfg.sendkey ?? '').slice(0, 10)}…`
|
||||
case 'bark':
|
||||
@@ -202,7 +202,6 @@ function channelSummary(ch: NotifyChannel): string {
|
||||
const EMPTY_CONFIG: Record<NotifyChannel['type'], Record<string, string>> = {
|
||||
telegram: {
|
||||
bot_token: '',
|
||||
chat_id: '',
|
||||
admin_user_ids: '',
|
||||
group_chat_id: '',
|
||||
channel_chat_id: '',
|
||||
@@ -215,6 +214,18 @@ const EMPTY_CONFIG: Record<NotifyChannel['type'], Record<string, string>> = {
|
||||
email: { smtp_host: '', smtp_port: '465', username: '', password: '', from: '', to: '', tls: 'true' },
|
||||
}
|
||||
|
||||
function normalizeInitialConfig(type: NotifyChannel['type'], raw: Record<string, unknown>): Record<string, string> {
|
||||
const base = { ...EMPTY_CONFIG[type] }
|
||||
for (const [key, value] of Object.entries(raw ?? {})) {
|
||||
base[key] = String(value ?? '')
|
||||
}
|
||||
if (type === 'telegram' && !base.group_chat_id && !base.channel_chat_id && base.chat_id?.startsWith('-')) {
|
||||
base.group_chat_id = base.chat_id
|
||||
}
|
||||
delete base.chat_id
|
||||
return base
|
||||
}
|
||||
|
||||
function ChannelFormModal({
|
||||
editing,
|
||||
onClose,
|
||||
@@ -229,7 +240,7 @@ function ChannelFormModal({
|
||||
editing?.type ?? 'telegram',
|
||||
)
|
||||
const [config, setConfig] = useState<Record<string, string>>(
|
||||
{ ...EMPTY_CONFIG[editing?.type ?? 'telegram'], ...(editing?.config ?? {}) },
|
||||
normalizeInitialConfig(editing?.type ?? 'telegram', editing?.config ?? {}),
|
||||
)
|
||||
const [enabled, setEnabled] = useState(editing?.enabled ?? true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
@@ -246,33 +257,21 @@ function ChannelFormModal({
|
||||
toast.error('请填写 Telegram Bot Token')
|
||||
return
|
||||
}
|
||||
if (!String(config.chat_id ?? '').trim()) {
|
||||
toast.error('请填写通知 Chat ID')
|
||||
return
|
||||
}
|
||||
if (!String(config.admin_user_ids ?? '').trim()) {
|
||||
toast.error('请填写管理员 Telegram ID')
|
||||
return
|
||||
}
|
||||
const chatID = String(config.chat_id ?? '').trim()
|
||||
const groupChatID = String(config.group_chat_id ?? '').trim()
|
||||
const channelChatID = String(config.channel_chat_id ?? '').trim()
|
||||
if (!groupChatID && !channelChatID && !chatID.startsWith('-')) {
|
||||
toast.error('请至少填写绑定群组 ID 或绑定频道 ID;或把群组/频道负数 ID 填到 Chat ID')
|
||||
return
|
||||
}
|
||||
if (!groupChatID && !channelChatID && chatID.startsWith('-')) {
|
||||
config.group_chat_id = chatID
|
||||
}
|
||||
}
|
||||
setSaving(true)
|
||||
try {
|
||||
const cleanedConfig = Object.fromEntries(
|
||||
Object.entries(config).map(([key, value]) => [key, String(value ?? '').trim()]),
|
||||
)
|
||||
delete cleanedConfig.chat_id
|
||||
const input: NotifyChannelInput = {
|
||||
name: name.trim(),
|
||||
type: type,
|
||||
config: Object.fromEntries(
|
||||
Object.entries(config).map(([key, value]) => [key, String(value ?? '').trim()]),
|
||||
),
|
||||
config: cleanedConfig,
|
||||
enabled,
|
||||
}
|
||||
if (editing) {
|
||||
@@ -336,15 +335,6 @@ function ChannelFormModal({
|
||||
onChange={(e) => updateConfig('bot_token', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Chat ID">
|
||||
<input
|
||||
required
|
||||
className="input-base"
|
||||
placeholder="-100123456"
|
||||
value={config.chat_id ?? ''}
|
||||
onChange={(e) => updateConfig('chat_id', e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="管理员 Telegram ID">
|
||||
<input
|
||||
required
|
||||
@@ -357,7 +347,7 @@ function ChannelFormModal({
|
||||
<Field label="绑定群组 ID">
|
||||
<input
|
||||
className="input-base"
|
||||
placeholder="如 -1001234567890;群组成员才允许唤醒/绑定"
|
||||
placeholder="选填,如 -1001234567890;填写后群组成员可唤醒/绑定"
|
||||
value={config.group_chat_id ?? ''}
|
||||
onChange={(e) => updateConfig('group_chat_id', e.target.value)}
|
||||
/>
|
||||
@@ -365,7 +355,7 @@ function ChannelFormModal({
|
||||
<Field label="绑定频道 ID">
|
||||
<input
|
||||
className="input-base"
|
||||
placeholder="如 -1009876543210;频道成员才允许唤醒/绑定"
|
||||
placeholder="选填,如 -1009876543210;填写后频道成员可唤醒/绑定"
|
||||
value={config.channel_chat_id ?? ''}
|
||||
onChange={(e) => updateConfig('channel_chat_id', e.target.value)}
|
||||
/>
|
||||
@@ -387,7 +377,7 @@ function ChannelFormModal({
|
||||
/>
|
||||
</Field>
|
||||
<div className="rounded-2xl border border-primary-400/15 bg-primary-400/5 px-4 py-3 text-xs leading-6 text-ink-50">
|
||||
必须至少填写群组 ID 或频道 ID。只有配置群组/频道中的成员可以唤醒 Bot、使用 <code>/start 用户名 密码</code> 绑定账号和隐藏成人目录;<code>/status</code>、<code>/search</code>、<code>/downloads</code>、<code>/stats</code> 仅管理员 Telegram ID 或已绑定的本地管理员可用。若测试通知超时,可填写反代 API 地址或代理地址。
|
||||
群组 ID、频道 ID 均为选填,可填一个、两个都填,也可以不填。不填时只有管理员 Telegram ID 可以私聊 Bot 使用;填写后,对应群组/频道成员可唤醒 Bot、使用 <code>/start 用户名 密码</code> 绑定账号和隐藏成人目录。<code>/status</code>、<code>/search</code>、<code>/downloads</code>、<code>/stats</code> 仍仅管理员 Telegram ID 或已绑定的本地管理员可用。若测试通知超时,可填写反代 API 地址或代理地址。
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user