mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-25 23:35:00 +03:00
feat(tgbot): route admin notifications to a forum topic (#6879)
Admins who run the bot in a forum supergroup got every notification in the General topic. An admin chat ID may now carry a topic, chatId:topicId, and the bot's admin messages and reports go to that topic. Repeated chats collapse into one entry and a second, different topic for the same chat is rejected, in the panel and in the docs setup helper alike. The chat ID hint in every locale and the English, Persian, Russian and Chinese guides describe the form.
This commit is contained in:
@@ -77,7 +77,7 @@ export function TelegramSetupHelper() {
|
|||||||
/>
|
/>
|
||||||
{adminIds ? (
|
{adminIds ? (
|
||||||
idsV.invalid.length > 0 ? (
|
idsV.invalid.length > 0 ? (
|
||||||
<Status ok={false}>Not numeric: {idsV.invalid.join(', ')}</Status>
|
<Status ok={false}>Invalid: {idsV.invalid.join(', ')}</Status>
|
||||||
) : (
|
) : (
|
||||||
<Status ok>
|
<Status ok>
|
||||||
{idsV.ids.length} admin id{idsV.ids.length === 1 ? '' : 's'}
|
{idsV.ids.length} admin id{idsV.ids.length === 1 ? '' : 's'}
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ connected). This is your **admin** ID.
|
|||||||
|
|
||||||
In Panel Settings, enable the Telegram bot and set the **token** and **admin chat
|
In Panel Settings, enable the Telegram bot and set the **token** and **admin chat
|
||||||
ID(s)** (comma-separated). Save, then message your bot.
|
ID(s)** (comma-separated). Save, then message your bot.
|
||||||
|
|
||||||
|
To send notifications into a specific topic of a forum supergroup, append the
|
||||||
|
topic ID after a colon, e.g. `-1001234567890:42`. The topic ID is the number
|
||||||
|
after the chat in a topic message link (`https://t.me/c/1234567890/42/…`). Leave
|
||||||
|
the topic off (or use `1`) for the General topic.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -101,7 +106,7 @@ The warning lead times come from `expireDiff` (days before expiry) and
|
|||||||
| -------------- | -------------------------- | ---------------------------------------------- |
|
| -------------- | -------------------------- | ---------------------------------------------- |
|
||||||
| `tgBotEnable` | `false` | Master on/off. |
|
| `tgBotEnable` | `false` | Master on/off. |
|
||||||
| `tgBotToken` | _(secret)_ | Bot API token. |
|
| `tgBotToken` | _(secret)_ | Bot API token. |
|
||||||
| `tgBotChatId` | _(none)_ | Comma-separated **admin** Telegram IDs. |
|
| `tgBotChatId` | _(none)_ | Comma-separated **admin** Telegram IDs; `chatId:topicId` targets a forum topic. |
|
||||||
| `tgBotProxy` | _(none)_ | `socks5://`, `http://`, or `https://` proxy. |
|
| `tgBotProxy` | _(none)_ | `socks5://`, `http://`, or `https://` proxy. |
|
||||||
| `tgBotAPIServer` | _(default)_ | Custom Telegram Bot API server. |
|
| `tgBotAPIServer` | _(default)_ | Custom Telegram Bot API server. |
|
||||||
| `tgRunTime` | `@daily` | Report schedule (cron / `@daily` / `@every …`).|
|
| `tgRunTime` | `@daily` | Report schedule (cron / `@daily` / `@every …`).|
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ icon: Send
|
|||||||
|
|
||||||
در تنظیمات پنل، ربات Telegram را فعال کنید و **توکن** و **شناسه(های) چت ادمین** را
|
در تنظیمات پنل، ربات Telegram را فعال کنید و **توکن** و **شناسه(های) چت ادمین** را
|
||||||
(با کاما جداشده) تنظیم نمایید. ذخیره کنید، سپس به ربات خود پیام بدهید.
|
(با کاما جداشده) تنظیم نمایید. ذخیره کنید، سپس به ربات خود پیام بدهید.
|
||||||
|
|
||||||
|
برای ارسال اعلانها به یک تاپیک مشخص از یک سوپرگروه فروم، شناسهی تاپیک را پس از
|
||||||
|
دونقطه اضافه کنید، مثلاً `-1001234567890:42`. شناسهی تاپیک عدد بعد از چت در لینک یک
|
||||||
|
پیام تاپیک است (`https://t.me/c/1234567890/42/…`). برای تاپیک General آن را ننویسید
|
||||||
|
(یا `1` بگذارید).
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -101,7 +106,7 @@ icon: Send
|
|||||||
| -------------- | -------------------------- | ---------------------------------------------- |
|
| -------------- | -------------------------- | ---------------------------------------------- |
|
||||||
| `tgBotEnable` | `false` | روشن/خاموش اصلی. |
|
| `tgBotEnable` | `false` | روشن/خاموش اصلی. |
|
||||||
| `tgBotToken` | _(محرمانه)_ | توکن API ربات. |
|
| `tgBotToken` | _(محرمانه)_ | توکن API ربات. |
|
||||||
| `tgBotChatId` | _(هیچکدام)_ | شناسههای Telegram **ادمین**، جداشده با کاما. |
|
| `tgBotChatId` | _(هیچکدام)_ | شناسههای Telegram **ادمین**، جداشده با کاما؛ `chatId:topicId` به یک تاپیک فروم میفرستد. |
|
||||||
| `tgBotProxy` | _(هیچکدام)_ | پروکسی `socks5://`، `http://` یا `https://`. |
|
| `tgBotProxy` | _(هیچکدام)_ | پروکسی `socks5://`، `http://` یا `https://`. |
|
||||||
| `tgBotAPIServer` | _(پیشفرض)_ | سرور سفارشی Telegram Bot API. |
|
| `tgBotAPIServer` | _(پیشفرض)_ | سرور سفارشی Telegram Bot API. |
|
||||||
| `tgRunTime` | `@daily` | زمانبندی گزارش (cron / `@daily` / `@every …`). |
|
| `tgRunTime` | `@daily` | زمانبندی گزارش (cron / `@daily` / `@every …`). |
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ icon: Send
|
|||||||
|
|
||||||
В настройках панели включите Telegram-бота и задайте **токен** и **админские
|
В настройках панели включите Telegram-бота и задайте **токен** и **админские
|
||||||
chat ID** (через запятую). Сохраните, затем напишите своему боту.
|
chat ID** (через запятую). Сохраните, затем напишите своему боту.
|
||||||
|
|
||||||
|
Чтобы отправлять уведомления в определённую тему форумной супергруппы, добавьте
|
||||||
|
ID темы после двоеточия, например `-1001234567890:42`. ID темы — число после чата
|
||||||
|
в ссылке на сообщение темы (`https://t.me/c/1234567890/42/…`). Для темы General
|
||||||
|
не указывайте его (или укажите `1`).
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -108,7 +113,7 @@ chat ID** (через запятую). Сохраните, затем напиш
|
|||||||
| -------------- | -------------------------- | ---------------------------------------------- |
|
| -------------- | -------------------------- | ---------------------------------------------- |
|
||||||
| `tgBotEnable` | `false` | Главный переключатель вкл./выкл. |
|
| `tgBotEnable` | `false` | Главный переключатель вкл./выкл. |
|
||||||
| `tgBotToken` | _(секрет)_ | Токен Bot API. |
|
| `tgBotToken` | _(секрет)_ | Токен Bot API. |
|
||||||
| `tgBotChatId` | _(нет)_ | **Админские** Telegram ID через запятую. |
|
| `tgBotChatId` | _(нет)_ | **Админские** Telegram ID через запятую; `chatId:topicId` — в тему форума. |
|
||||||
| `tgBotProxy` | _(нет)_ | Прокси `socks5://`, `http://` или `https://`. |
|
| `tgBotProxy` | _(нет)_ | Прокси `socks5://`, `http://` или `https://`. |
|
||||||
| `tgBotAPIServer` | _(по умолчанию)_ | Пользовательский сервер Telegram Bot API. |
|
| `tgBotAPIServer` | _(по умолчанию)_ | Пользовательский сервер Telegram Bot API. |
|
||||||
| `tgRunTime` | `@daily` | Расписание отчётов (cron / `@daily` / `@every …`).|
|
| `tgRunTime` | `@daily` | Расписание отчётов (cron / `@daily` / `@every …`).|
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ icon: Send
|
|||||||
|
|
||||||
在面板设置中启用 Telegram 机器人,并填写**令牌**和**管理员聊天 ID**
|
在面板设置中启用 Telegram 机器人,并填写**令牌**和**管理员聊天 ID**
|
||||||
(多个时以逗号分隔)。保存后,再向你的机器人发送消息。
|
(多个时以逗号分隔)。保存后,再向你的机器人发送消息。
|
||||||
|
|
||||||
|
要把通知发送到论坛超级群组的某个话题,在聊天 ID 后加冒号和话题 ID,例如
|
||||||
|
`-1001234567890:42`。话题 ID 是话题消息链接中聊天之后的数字
|
||||||
|
(`https://t.me/c/1234567890/42/…`)。General 话题不填(或填 `1`)。
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -97,7 +101,7 @@ ID 的所有客户端。订阅 ID 就是邀请码,因此请保持其足够长
|
|||||||
| -------------- | -------------------------- | ---------------------------------------------- |
|
| -------------- | -------------------------- | ---------------------------------------------- |
|
||||||
| `tgBotEnable` | `false` | 总开关。 |
|
| `tgBotEnable` | `false` | 总开关。 |
|
||||||
| `tgBotToken` | _(机密)_ | 机器人 API 令牌。 |
|
| `tgBotToken` | _(机密)_ | 机器人 API 令牌。 |
|
||||||
| `tgBotChatId` | _(无)_ | 以逗号分隔的**管理员** Telegram ID。 |
|
| `tgBotChatId` | _(无)_ | 以逗号分隔的**管理员** Telegram ID;`chatId:topicId` 发送到论坛话题。 |
|
||||||
| `tgBotProxy` | _(无)_ | `socks5://`、`http://` 或 `https://` 代理。 |
|
| `tgBotProxy` | _(无)_ | `socks5://`、`http://` 或 `https://` 代理。 |
|
||||||
| `tgBotAPIServer` | _(默认)_ | 自定义 Telegram Bot API 服务器。 |
|
| `tgBotAPIServer` | _(默认)_ | 自定义 Telegram Bot API 服务器。 |
|
||||||
| `tgRunTime` | `@daily` | 报告计划(cron / `@daily` / `@every …`)。 |
|
| `tgRunTime` | `@daily` | 报告计划(cron / `@daily` / `@every …`)。 |
|
||||||
|
|||||||
@@ -32,18 +32,47 @@ describe('validateBotToken', () => {
|
|||||||
|
|
||||||
describe('parseAdminIds', () => {
|
describe('parseAdminIds', () => {
|
||||||
it('splits a comma list into integer ids', () => {
|
it('splits a comma list into integer ids', () => {
|
||||||
expect(parseAdminIds('111, 222')).toEqual({ ids: [111, 222], invalid: [] });
|
expect(parseAdminIds('111, 222')).toEqual({
|
||||||
|
ids: [111, 222],
|
||||||
|
entries: ['111', '222'],
|
||||||
|
invalid: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts negative group ids and captures invalid entries', () => {
|
it('accepts negative group ids and captures invalid entries', () => {
|
||||||
expect(parseAdminIds('-1001234567, abc, 42')).toEqual({
|
expect(parseAdminIds('-1001234567, abc, 42')).toEqual({
|
||||||
ids: [-1001234567, 42],
|
ids: [-1001234567, 42],
|
||||||
|
entries: ['-1001234567', '42'],
|
||||||
invalid: ['abc'],
|
invalid: ['abc'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('drops the General topic suffix, since the Bot API rejects thread id 1', () => {
|
||||||
|
expect(parseAdminIds('-1001234567:1')).toEqual({
|
||||||
|
ids: [-1001234567],
|
||||||
|
entries: ['-1001234567'],
|
||||||
|
invalid: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a second topic for the same chat, as the panel does', () => {
|
||||||
|
expect(parseAdminIds('-1001:7, -1001:1, -1001:8')).toEqual({
|
||||||
|
ids: [-1001],
|
||||||
|
entries: ['-1001:7'],
|
||||||
|
invalid: ['-1001:8'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps a forum topic suffix and rejects a malformed one', () => {
|
||||||
|
expect(parseAdminIds('-1001234567:42, 111:0, 222:x')).toEqual({
|
||||||
|
ids: [-1001234567],
|
||||||
|
entries: ['-1001234567:42'],
|
||||||
|
invalid: ['111:0', '222:x'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('returns empty for blank input', () => {
|
it('returns empty for blank input', () => {
|
||||||
expect(parseAdminIds(' ')).toEqual({ ids: [], invalid: [] });
|
expect(parseAdminIds(' ')).toEqual({ ids: [], entries: [], invalid: [] });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,4 +132,13 @@ describe('buildBotConfigSummary', () => {
|
|||||||
expect(s.tgBotChatId).toBe('111,222');
|
expect(s.tgBotChatId).toBe('111,222');
|
||||||
expect(s.tgRunTime).toBe('@daily');
|
expect(s.tgRunTime).toBe('@daily');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps forum topic suffixes in the chat id setting', () => {
|
||||||
|
const s = buildBotConfigSummary({
|
||||||
|
token: VALID_TOKEN,
|
||||||
|
adminIds: '111, -1001234567 : 42',
|
||||||
|
runTime: '@daily',
|
||||||
|
});
|
||||||
|
expect(s.tgBotChatId).toBe('111,-1001234567:42');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Pure validation + templating helpers for 3x-ui's Telegram bot settings.
|
// Pure validation + templating helpers for 3x-ui's Telegram bot settings.
|
||||||
// Grounded in internal/web/service/tgbot/tgbot.go (admin ids parsed with
|
// Grounded in internal/web/service/tgbot/tgbot.go (admin ids parsed by
|
||||||
// strconv.ParseInt(_,10,64); token handed to telego.NewBot → api.telegram.org)
|
// parseAdminChats as "chatId[:topicId]"; token handed to telego.NewBot → api.telegram.org)
|
||||||
// and the panel's tg* settings (tgRunTime uses robfig/cron). No React/DOM.
|
// and the panel's tg* settings (tgRunTime uses robfig/cron). No React/DOM.
|
||||||
|
|
||||||
export interface BotConfig {
|
export interface BotConfig {
|
||||||
@@ -17,6 +17,7 @@ export interface TokenValidation {
|
|||||||
|
|
||||||
export interface AdminIdsResult {
|
export interface AdminIdsResult {
|
||||||
ids: number[];
|
ids: number[];
|
||||||
|
entries: string[]; // normalized "chatId" or "chatId:topicId", as the panel stores them
|
||||||
invalid: string[];
|
invalid: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +42,29 @@ export function validateBotToken(token: string): TokenValidation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseAdminIds(raw: string): AdminIdsResult {
|
export function parseAdminIds(raw: string): AdminIdsResult {
|
||||||
const ids: number[] = [];
|
// Mirrors parseAdminChats in tgbot.go: one topic per chat, repeats collapse.
|
||||||
|
const topics = new Map<string, number>();
|
||||||
const invalid: string[] = [];
|
const invalid: string[] = [];
|
||||||
for (const part of raw
|
for (const part of raw
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean)) {
|
.filter(Boolean)) {
|
||||||
// Telegram chat ids are integers; group/channel ids are negative.
|
// Telegram chat ids are integers; group/channel ids are negative. An
|
||||||
if (/^-?\d+$/.test(part)) ids.push(Number(part));
|
// optional ":<topicId>" targets a forum topic inside a supergroup.
|
||||||
else invalid.push(part);
|
const m = /^(-?\d+)\s*(?::\s*(\d+))?$/.exec(part);
|
||||||
|
if (!m || (m[2] !== undefined && Number(m[2]) <= 0)) {
|
||||||
|
invalid.push(part);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// General (thread 1) is reached by omitting the topic; the Bot API rejects 1.
|
||||||
|
const topic = m[2] === undefined || Number(m[2]) === 1 ? 0 : Number(m[2]);
|
||||||
|
const prev = topics.get(m[1]) ?? 0;
|
||||||
|
if (topic && prev && prev !== topic) invalid.push(part);
|
||||||
|
else topics.set(m[1], topic || prev);
|
||||||
}
|
}
|
||||||
return { ids, invalid };
|
const ids = [...topics.keys()].map(Number);
|
||||||
|
const entries = [...topics].map(([chat, topic]) => (topic ? `${chat}:${topic}` : chat));
|
||||||
|
return { ids, entries, invalid };
|
||||||
}
|
}
|
||||||
|
|
||||||
// robfig/cron predefined macros (note: @reboot is NOT supported).
|
// robfig/cron predefined macros (note: @reboot is NOT supported).
|
||||||
@@ -108,11 +121,11 @@ export function buildBotConfigSummary(c: BotConfig): {
|
|||||||
tgBotChatId: string;
|
tgBotChatId: string;
|
||||||
tgRunTime: string;
|
tgRunTime: string;
|
||||||
} {
|
} {
|
||||||
const { ids } = parseAdminIds(c.adminIds);
|
const { entries } = parseAdminIds(c.adminIds);
|
||||||
return {
|
return {
|
||||||
tgBotEnable: true,
|
tgBotEnable: true,
|
||||||
tgBotToken: c.token.trim(),
|
tgBotToken: c.token.trim(),
|
||||||
tgBotChatId: ids.join(','),
|
tgBotChatId: entries.join(','),
|
||||||
tgRunTime: c.runTime.trim(),
|
tgRunTime: c.runTime.trim(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -40,6 +41,7 @@ var (
|
|||||||
|
|
||||||
botHandler *th.BotHandler
|
botHandler *th.BotHandler
|
||||||
adminIds []int64
|
adminIds []int64
|
||||||
|
adminTopics map[int64]int // admin chat -> forum topic (message_thread_id)
|
||||||
isRunning bool
|
isRunning bool
|
||||||
hostname string
|
hostname string
|
||||||
hashStorage *global.HashStorage
|
hashStorage *global.HashStorage
|
||||||
@@ -321,20 +323,14 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedAdminIds := make([]int64, 0)
|
parsedAdminIds, parsedAdminTopics, err := parseAdminChats(tgBotID)
|
||||||
// Parse admin IDs from comma-separated string
|
if err != nil {
|
||||||
if tgBotID != "" {
|
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
|
||||||
for adminID := range strings.SplitSeq(tgBotID, ",") {
|
return err
|
||||||
id, err := strconv.ParseInt(adminID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
parsedAdminIds = append(parsedAdminIds, id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tgBotMutex.Lock()
|
tgBotMutex.Lock()
|
||||||
adminIds = parsedAdminIds
|
adminIds = parsedAdminIds
|
||||||
|
adminTopics = parsedAdminTopics
|
||||||
tgBotMutex.Unlock()
|
tgBotMutex.Unlock()
|
||||||
|
|
||||||
// Get Telegram bot proxy URL
|
// Get Telegram bot proxy URL
|
||||||
@@ -500,6 +496,61 @@ func adminSnapshot() []int64 {
|
|||||||
return slices.Clone(adminIds)
|
return slices.Clone(adminIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseAdminChats reads "chatId[:topicId],..."; the optional topic targets a
|
||||||
|
// forum topic in a supergroup, e.g. "123456789,-1001234567890:42".
|
||||||
|
func parseAdminChats(raw string) ([]int64, map[int64]int, error) {
|
||||||
|
ids := make([]int64, 0)
|
||||||
|
topics := make(map[int64]int)
|
||||||
|
if strings.TrimSpace(raw) == "" {
|
||||||
|
return ids, topics, nil
|
||||||
|
}
|
||||||
|
for entry := range strings.SplitSeq(raw, ",") {
|
||||||
|
entry = strings.TrimSpace(entry)
|
||||||
|
chatPart, topicPart, hasTopic := strings.Cut(entry, ":")
|
||||||
|
id, err := strconv.ParseInt(strings.TrimSpace(chatPart), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
topic := 0
|
||||||
|
if hasTopic {
|
||||||
|
topic, err = strconv.Atoi(strings.TrimSpace(topicPart))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if topic <= 0 {
|
||||||
|
return nil, nil, fmt.Errorf("invalid topic ID %q for chat %d", topicPart, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// General is thread 1 internally, but the Bot API only reaches it with no thread id.
|
||||||
|
if hasTopic && topic != generalTopicID {
|
||||||
|
if prev, ok := topics[id]; ok && prev != topic {
|
||||||
|
return nil, nil, fmt.Errorf("chat %d has more than one topic configured", id)
|
||||||
|
}
|
||||||
|
topics[id] = topic
|
||||||
|
}
|
||||||
|
if !slices.Contains(ids, id) {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids, topics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const generalTopicID = 1
|
||||||
|
|
||||||
|
// adminTopic returns the forum topic configured for chatId, or 0 when the
|
||||||
|
// chat has none and messages should go to the chat itself.
|
||||||
|
func adminTopic(chatId int64) int {
|
||||||
|
tgBotMutex.Lock()
|
||||||
|
defer tgBotMutex.Unlock()
|
||||||
|
return adminTopics[chatId]
|
||||||
|
}
|
||||||
|
|
||||||
|
// inAdminTopic routes a document to the configured forum topic of its chat.
|
||||||
|
func inAdminTopic(document *telego.SendDocumentParams) *telego.SendDocumentParams {
|
||||||
|
document.MessageThreadID = adminTopic(document.ChatID.ID)
|
||||||
|
return document
|
||||||
|
}
|
||||||
|
|
||||||
// SetHostname sets the hostname for the bot.
|
// SetHostname sets the hostname for the bot.
|
||||||
func (t *Tgbot) SetHostname() {
|
func (t *Tgbot) SetHostname() {
|
||||||
host, err := os.Hostname()
|
host, err := os.Hostname()
|
||||||
@@ -518,6 +569,7 @@ func (t *Tgbot) Stop() {
|
|||||||
logger.Info("Stop Telegram receiver ...")
|
logger.Info("Stop Telegram receiver ...")
|
||||||
tgBotMutex.Lock()
|
tgBotMutex.Lock()
|
||||||
adminIds = nil
|
adminIds = nil
|
||||||
|
adminTopics = nil
|
||||||
tgBotMutex.Unlock()
|
tgBotMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
|
|||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.FileFromBytes(png, "sub.png"),
|
tu.FileFromBytes(png, "sub.png"),
|
||||||
)
|
)
|
||||||
_, _ = bot.SendDocument(context.Background(), document)
|
_, _ = bot.SendDocument(context.Background(), inAdminTopic(document))
|
||||||
} else {
|
} else {
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
|
|||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.FileFromBytes(png, "subjson.png"),
|
tu.FileFromBytes(png, "subjson.png"),
|
||||||
)
|
)
|
||||||
_, _ = bot.SendDocument(context.Background(), document)
|
_, _ = bot.SendDocument(context.Background(), inAdminTopic(document))
|
||||||
} else {
|
} else {
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
||||||
}
|
}
|
||||||
@@ -349,7 +349,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
|
|||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.FileFromBytes(png, filename),
|
tu.FileFromBytes(png, filename),
|
||||||
)
|
)
|
||||||
_, _ = bot.SendDocument(context.Background(), document)
|
_, _ = bot.SendDocument(context.Background(), inAdminTopic(document))
|
||||||
// Reduced delay for better performance
|
// Reduced delay for better performance
|
||||||
if i < max-1 { // Only delay between documents, not after the last one
|
if i < max-1 { // Only delay between documents, not after the last one
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ func (t *Tgbot) sendBackupData(chatId int64, dbData []byte, dbFilename string) {
|
|||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.FileFromBytes(dbData, dbFilename),
|
tu.FileFromBytes(dbData, dbFilename),
|
||||||
)
|
)
|
||||||
_, err := bot.SendDocument(ctx, document)
|
_, err := bot.SendDocument(ctx, inAdminTopic(document))
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading backup: ", err)
|
logger.Error("Error in uploading backup: ", err)
|
||||||
@@ -452,7 +452,7 @@ func (t *Tgbot) sendBackupData(chatId int64, dbData []byte, dbFilename string) {
|
|||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.File(file),
|
tu.File(file),
|
||||||
)
|
)
|
||||||
_, err = bot.SendDocument(ctx, document)
|
_, err = bot.SendDocument(ctx, inAdminTopic(document))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading config.json: ", err)
|
logger.Error("Error in uploading config.json: ", err)
|
||||||
}
|
}
|
||||||
@@ -478,7 +478,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.File(file),
|
tu.File(file),
|
||||||
)
|
)
|
||||||
_, err = bot.SendDocument(context.Background(), document)
|
_, err = bot.SendDocument(context.Background(), inAdminTopic(document))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
||||||
}
|
}
|
||||||
@@ -499,7 +499,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.File(file),
|
tu.File(file),
|
||||||
)
|
)
|
||||||
_, err = bot.SendDocument(context.Background(), document)
|
_, err = bot.SendDocument(context.Background(), inAdminTopic(document))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,9 +133,10 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
|
|||||||
allMessages := pageMessage(msg, telegramPageLimit)
|
allMessages := pageMessage(msg, telegramPageLimit)
|
||||||
for n, message := range allMessages {
|
for n, message := range allMessages {
|
||||||
params := telego.SendMessageParams{
|
params := telego.SendMessageParams{
|
||||||
ChatID: tu.ID(chatId),
|
ChatID: tu.ID(chatId),
|
||||||
Text: message,
|
MessageThreadID: adminTopic(chatId),
|
||||||
ParseMode: "HTML",
|
Text: message,
|
||||||
|
ParseMode: "HTML",
|
||||||
}
|
}
|
||||||
// only add replyMarkup to last message
|
// only add replyMarkup to last message
|
||||||
if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
|
if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
|
||||||
@@ -260,9 +261,10 @@ func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSecon
|
|||||||
|
|
||||||
// Send the message
|
// Send the message
|
||||||
sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{
|
sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{
|
||||||
ChatID: tu.ID(chatId),
|
ChatID: tu.ID(chatId),
|
||||||
Text: msg,
|
MessageThreadID: adminTopic(chatId),
|
||||||
ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value
|
Text: msg,
|
||||||
|
ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Failed to send message:", err)
|
logger.Warning("Failed to send message:", err)
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mymmrac/telego"
|
||||||
|
tu "github.com/mymmrac/telego/telegoutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoginAttemptDoesNotCarryPassword(t *testing.T) {
|
func TestLoginAttemptDoesNotCarryPassword(t *testing.T) {
|
||||||
@@ -154,3 +158,67 @@ func TestIsCommandForBotKeepsLegacyBehaviorWhenUsernameUnavailable(t *testing.T)
|
|||||||
t.Fatal("commands must remain accepted when the current bot username is unavailable")
|
t.Fatal("commands must remain accepted when the current bot username is unavailable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseAdminChats(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
raw string
|
||||||
|
ids []int64
|
||||||
|
topics map[int64]int
|
||||||
|
}{
|
||||||
|
{name: "empty", raw: "", ids: []int64{}, topics: map[int64]int{}},
|
||||||
|
{name: "plain ids", raw: "123,-1001", ids: []int64{123, -1001}, topics: map[int64]int{}},
|
||||||
|
{name: "topic", raw: "-1003855200872:42", ids: []int64{-1003855200872}, topics: map[int64]int{-1003855200872: 42}},
|
||||||
|
{name: "mixed with spaces", raw: " 123 , -1001 : 7 ", ids: []int64{123, -1001}, topics: map[int64]int{-1001: 7}},
|
||||||
|
{name: "general topic means no thread", raw: "-1001:1,123", ids: []int64{-1001, 123}, topics: map[int64]int{}},
|
||||||
|
{name: "duplicate chat collapses", raw: "-1001:7,-1001:7,-1001", ids: []int64{-1001}, topics: map[int64]int{-1001: 7}},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ids, topics, err := parseAdminChats(tc.raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseAdminChats(%q) error: %v", tc.raw, err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(ids, tc.ids) {
|
||||||
|
t.Fatalf("ids = %v, want %v", ids, tc.ids)
|
||||||
|
}
|
||||||
|
if len(topics) != len(tc.topics) {
|
||||||
|
t.Fatalf("topics = %v, want %v", topics, tc.topics)
|
||||||
|
}
|
||||||
|
for chat, topic := range tc.topics {
|
||||||
|
if topics[chat] != topic {
|
||||||
|
t.Fatalf("topics[%d] = %d, want %d", chat, topics[chat], topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAdminChatsRejectsInvalid(t *testing.T) {
|
||||||
|
for _, raw := range []string{"abc", "123:", "123:abc", "123:0", "123:-5", "-1001:7,-1001:8", "123,"} {
|
||||||
|
if _, _, err := parseAdminChats(raw); err == nil {
|
||||||
|
t.Errorf("parseAdminChats(%q) accepted invalid input", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInAdminTopicRoutesConfiguredChat(t *testing.T) {
|
||||||
|
tgBotMutex.Lock()
|
||||||
|
orig := adminTopics
|
||||||
|
adminTopics = map[int64]int{-1001: 42}
|
||||||
|
tgBotMutex.Unlock()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
tgBotMutex.Lock()
|
||||||
|
adminTopics = orig
|
||||||
|
tgBotMutex.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
doc := inAdminTopic(tu.Document(tu.ID(-1001), tu.FileFromBytes([]byte("x"), "x.db")))
|
||||||
|
if doc.MessageThreadID != 42 {
|
||||||
|
t.Fatalf("MessageThreadID = %d, want 42", doc.MessageThreadID)
|
||||||
|
}
|
||||||
|
doc = inAdminTopic(&telego.SendDocumentParams{ChatID: tu.ID(123)})
|
||||||
|
if doc.MessageThreadID != 0 {
|
||||||
|
t.Fatalf("MessageThreadID = %d for chat without topic, want 0", doc.MessageThreadID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "خادم API لتيليجرام",
|
"telegramAPIServer": "خادم API لتيليجرام",
|
||||||
"telegramAPIServerDesc": "سيرفر Telegram API المستخدم. سيبه فاضي لاستخدام الافتراضي.",
|
"telegramAPIServerDesc": "سيرفر Telegram API المستخدم. سيبه فاضي لاستخدام الافتراضي.",
|
||||||
"telegramChatId": "ID شات الأدمن",
|
"telegramChatId": "ID شات الأدمن",
|
||||||
"telegramChatIdDesc": "ID شات الأدمن في Telegram. (مفصول بفواصل)(تقدر تجيبه من {'@'}userinfobot) أو (استخدم '/id' في البوت)",
|
"telegramChatIdDesc": "ID شات الأدمن في Telegram. (مفصول بفواصل)(تقدر تجيبه من {'@'}userinfobot) أو (استخدم '/id' في البوت) للنشر في موضوع منتدى، أضف ':topicId'، مثل -1001234567890:42",
|
||||||
"telegramNotifyTime": "وقت الإشعار",
|
"telegramNotifyTime": "وقت الإشعار",
|
||||||
"telegramNotifyTimeDesc": "عدد مرات إرسال البوت للتقارير الدورية. اختر فترة جاهزة، أو اختر «مخصص» لإدخال تعبير crontab.",
|
"telegramNotifyTimeDesc": "عدد مرات إرسال البوت للتقارير الدورية. اختر فترة جاهزة، أو اختر «مخصص» لإدخال تعبير crontab.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1343,7 +1343,7 @@
|
|||||||
"telegramAPIServer": "Telegram API Server",
|
"telegramAPIServer": "Telegram API Server",
|
||||||
"telegramAPIServerDesc": "The Telegram API server to use. Leave blank to use the default server.",
|
"telegramAPIServerDesc": "The Telegram API server to use. Leave blank to use the default server.",
|
||||||
"telegramChatId": "Admin Chat ID",
|
"telegramChatId": "Admin Chat ID",
|
||||||
"telegramChatIdDesc": "The Telegram Admin Chat ID(s). (comma-separated)(get it here {'@'}userinfobot) or (use '/id' command in the bot)",
|
"telegramChatIdDesc": "The Telegram Admin Chat ID(s). (comma-separated)(get it here {'@'}userinfobot) or (use '/id' command in the bot). To post into a forum topic, append ':topicId', e.g. -1001234567890:42",
|
||||||
"telegramNotifyTime": "Notification Time",
|
"telegramNotifyTime": "Notification Time",
|
||||||
"telegramNotifyTimeDesc": "How often the Telegram bot sends periodic reports. Pick a preset interval, or choose Custom to enter a raw crontab expression.",
|
"telegramNotifyTimeDesc": "How often the Telegram bot sends periodic reports. Pick a preset interval, or choose Custom to enter a raw crontab expression.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Servidor API de Telegram",
|
"telegramAPIServer": "Servidor API de Telegram",
|
||||||
"telegramAPIServerDesc": "El servidor API de Telegram a utilizar. Déjelo en blanco para utilizar el servidor predeterminado.",
|
"telegramAPIServerDesc": "El servidor API de Telegram a utilizar. Déjelo en blanco para utilizar el servidor predeterminado.",
|
||||||
"telegramChatId": "IDs de Chat de Telegram para Administradores",
|
"telegramChatId": "IDs de Chat de Telegram para Administradores",
|
||||||
"telegramChatIdDesc": "IDs de Chat múltiples separados por comas. Use {'@'}userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat.",
|
"telegramChatIdDesc": "IDs de Chat múltiples separados por comas. Use {'@'}userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat. Para publicar en un tema de foro, añada ':topicId', p. ej. -1001234567890:42",
|
||||||
"telegramNotifyTime": "Hora de Notificación del Bot de Telegram",
|
"telegramNotifyTime": "Hora de Notificación del Bot de Telegram",
|
||||||
"telegramNotifyTimeDesc": "Con qué frecuencia el bot de Telegram envía informes periódicos. Elige un intervalo predefinido o selecciona Personalizado para introducir una expresión crontab.",
|
"telegramNotifyTimeDesc": "Con qué frecuencia el bot de Telegram envía informes periódicos. Elige un intervalo predefinido o selecciona Personalizado para introducir una expresión crontab.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1224,7 +1224,7 @@
|
|||||||
"telegramAPIServer": "سرور API تلگرام",
|
"telegramAPIServer": "سرور API تلگرام",
|
||||||
"telegramAPIServerDesc": "API سرور تلگرام برای اتصال را تغییر میدهد. برای استفاده از سرور پیش فرض خالی بگذارید",
|
"telegramAPIServerDesc": "API سرور تلگرام برای اتصال را تغییر میدهد. برای استفاده از سرور پیش فرض خالی بگذارید",
|
||||||
"telegramChatId": "آیدی چت مدیر",
|
"telegramChatId": "آیدی چت مدیر",
|
||||||
"telegramChatIdDesc": "دریافت کنید ('/id'یا (دستور ({'@'}userinfobot) آیدی(های) چت تلگرام مدیر، از",
|
"telegramChatIdDesc": "آیدی(های) چت تلگرام مدیر، جدا شده با کاما. از {'@'}userinfobot یا دستور '/id' در ربات دریافت کنید. برای ارسال در یک تاپیک فروم، ':topicId' را اضافه کنید، مثلاً -1001234567890:42",
|
||||||
"telegramNotifyTime": "زمان نوتیفیکیشن",
|
"telegramNotifyTime": "زمان نوتیفیکیشن",
|
||||||
"telegramNotifyTimeDesc": "هر چند وقت یکبار ربات تلگرام گزارش دورهای بفرستد. یک بازهٔ آماده انتخاب کنید یا گزینهٔ سفارشی را بزنید تا عبارت crontab وارد کنید.",
|
"telegramNotifyTimeDesc": "هر چند وقت یکبار ربات تلگرام گزارش دورهای بفرستد. یک بازهٔ آماده انتخاب کنید یا گزینهٔ سفارشی را بزنید تا عبارت crontab وارد کنید.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Server API Telegram",
|
"telegramAPIServer": "Server API Telegram",
|
||||||
"telegramAPIServerDesc": "Server API Telegram yang akan digunakan. Biarkan kosong untuk menggunakan server default.",
|
"telegramAPIServerDesc": "Server API Telegram yang akan digunakan. Biarkan kosong untuk menggunakan server default.",
|
||||||
"telegramChatId": "ID Obrolan Admin",
|
"telegramChatId": "ID Obrolan Admin",
|
||||||
"telegramChatIdDesc": "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini {'@'}userinfobot) atau (gunakan perintah '/id' di bot)",
|
"telegramChatIdDesc": "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini {'@'}userinfobot) atau (gunakan perintah '/id' di bot). Untuk mengirim ke topik forum, tambahkan ':topicId', mis. -1001234567890:42",
|
||||||
"telegramNotifyTime": "Waktu Notifikasi",
|
"telegramNotifyTime": "Waktu Notifikasi",
|
||||||
"telegramNotifyTimeDesc": "Seberapa sering bot Telegram mengirim laporan berkala. Pilih interval siap pakai, atau pilih Kustom untuk memasukkan ekspresi crontab.",
|
"telegramNotifyTimeDesc": "Seberapa sering bot Telegram mengirim laporan berkala. Pilih interval siap pakai, atau pilih Kustom untuk memasukkan ekspresi crontab.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Telegram API サーバー",
|
"telegramAPIServer": "Telegram API サーバー",
|
||||||
"telegramAPIServerDesc": "使用するTelegram APIサーバー。空白の場合はデフォルトサーバーを使用する",
|
"telegramAPIServerDesc": "使用するTelegram APIサーバー。空白の場合はデフォルトサーバーを使用する",
|
||||||
"telegramChatId": "管理者チャットID",
|
"telegramChatId": "管理者チャットID",
|
||||||
"telegramChatIdDesc": "Telegram管理者チャットID(複数の場合はカンマで区切る){'@'}userinfobotで取得するか、ボットで'/id'コマンドを使用して取得する",
|
"telegramChatIdDesc": "Telegram管理者チャットID(複数の場合はカンマで区切る){'@'}userinfobotで取得するか、ボットで'/id'コマンドを使用して取得する フォーラムのトピックに投稿するには ':topicId' を付けます(例: -1001234567890:42)",
|
||||||
"telegramNotifyTime": "通知時間",
|
"telegramNotifyTime": "通知時間",
|
||||||
"telegramNotifyTimeDesc": "Telegram ボットが定期レポートを送信する頻度です。プリセットの間隔を選ぶか、「カスタム」を選んで crontab 式を入力します。",
|
"telegramNotifyTimeDesc": "Telegram ボットが定期レポートを送信する頻度です。プリセットの間隔を選ぶか、「カスタム」を選んで crontab 式を入力します。",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Servidor API do Telegram",
|
"telegramAPIServer": "Servidor API do Telegram",
|
||||||
"telegramAPIServerDesc": "O servidor API do Telegram a ser usado. Deixe em branco para usar o servidor padrão.",
|
"telegramAPIServerDesc": "O servidor API do Telegram a ser usado. Deixe em branco para usar o servidor padrão.",
|
||||||
"telegramChatId": "ID de Chat do Administrador",
|
"telegramChatId": "ID de Chat do Administrador",
|
||||||
"telegramChatIdDesc": "O(s) ID(s) de Chat do Administrador no Telegram. (separado por vírgulas)(obtenha aqui {'@'}userinfobot) ou (use o comando '/id' no bot)",
|
"telegramChatIdDesc": "O(s) ID(s) de Chat do Administrador no Telegram. (separado por vírgulas)(obtenha aqui {'@'}userinfobot) ou (use o comando '/id' no bot). Para publicar em um tópico de fórum, acrescente ':topicId', ex.: -1001234567890:42",
|
||||||
"telegramNotifyTime": "Hora da Notificação",
|
"telegramNotifyTime": "Hora da Notificação",
|
||||||
"telegramNotifyTimeDesc": "Com que frequência o bot do Telegram envia relatórios periódicos. Escolha um intervalo predefinido ou selecione Personalizado para inserir uma expressão crontab.",
|
"telegramNotifyTimeDesc": "Com que frequência o bot do Telegram envia relatórios periódicos. Escolha um intervalo predefinido ou selecione Personalizado para inserir uma expressão crontab.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Telegram API Server",
|
"telegramAPIServer": "Telegram API Server",
|
||||||
"telegramAPIServerDesc": "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию.",
|
"telegramAPIServerDesc": "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию.",
|
||||||
"telegramChatId": "User ID администратора бота",
|
"telegramChatId": "User ID администратора бота",
|
||||||
"telegramChatIdDesc": "Один или несколько User ID администратора(-ов) Telegram-бота. Для получения User ID используйте {'@'}userinfobot или команду '/id' в боте.",
|
"telegramChatIdDesc": "Один или несколько User ID администратора(-ов) Telegram-бота. Для получения User ID используйте {'@'}userinfobot или команду '/id' в боте. Чтобы отправлять в тему форума, добавьте ':topicId', например -1001234567890:42",
|
||||||
"telegramNotifyTime": "Частота уведомлений для администраторов от бота",
|
"telegramNotifyTime": "Частота уведомлений для администраторов от бота",
|
||||||
"telegramNotifyTimeDesc": "Как часто бот Telegram отправляет периодические отчёты. Выберите готовый интервал или «Произвольный», чтобы ввести выражение crontab.",
|
"telegramNotifyTimeDesc": "Как часто бот Telegram отправляет периодические отчёты. Выберите готовый интервал или «Произвольный», чтобы ввести выражение crontab.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Telegram API Sunucusu",
|
"telegramAPIServer": "Telegram API Sunucusu",
|
||||||
"telegramAPIServerDesc": "Kullanılacak Telegram API sunucusu. Varsayılan sunucuyu kullanmak için boş bırakın.",
|
"telegramAPIServerDesc": "Kullanılacak Telegram API sunucusu. Varsayılan sunucuyu kullanmak için boş bırakın.",
|
||||||
"telegramChatId": "Yönetici Sohbet Kimliği",
|
"telegramChatId": "Yönetici Sohbet Kimliği",
|
||||||
"telegramChatIdDesc": "Telegram Yönetici Sohbet Kimliği (Chat ID). Birden fazla ise virgülle ayırın. ({'@'}userinfobot'tan alabilirsiniz veya botta '/id' komutunu kullanabilirsiniz.)",
|
"telegramChatIdDesc": "Telegram Yönetici Sohbet Kimliği (Chat ID). Birden fazla ise virgülle ayırın. ({'@'}userinfobot'tan alabilirsiniz veya botta '/id' komutunu kullanabilirsiniz.) Bir forum konusuna göndermek için ':topicId' ekleyin, ör. -1001234567890:42",
|
||||||
"telegramNotifyTime": "Bildirim Zamanı",
|
"telegramNotifyTime": "Bildirim Zamanı",
|
||||||
"telegramNotifyTimeDesc": "Telegram botunun periyodik raporları gönderme sıklığı. Hazır bir aralık seçin veya bir crontab ifadesi girmek için Özel'i seçin.",
|
"telegramNotifyTimeDesc": "Telegram botunun periyodik raporları gönderme sıklığı. Hazır bir aralık seçin veya bir crontab ifadesi girmek için Özel'i seçin.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Telegram API сервер",
|
"telegramAPIServer": "Telegram API сервер",
|
||||||
"telegramAPIServerDesc": "Сервер Telegram API для використання. Залиште поле порожнім, щоб використовувати сервер за умовчанням.",
|
"telegramAPIServerDesc": "Сервер Telegram API для використання. Залиште поле порожнім, щоб використовувати сервер за умовчанням.",
|
||||||
"telegramChatId": "Ідентифікатор чату адміністратора",
|
"telegramChatId": "Ідентифікатор чату адміністратора",
|
||||||
"telegramChatIdDesc": "Ідентифікатори чату адміністратора Telegram. (розділені комами) (отримайте тут {'@'}userinfobot) або (використовуйте команду '/id' у боті)",
|
"telegramChatIdDesc": "Ідентифікатори чату адміністратора Telegram. (розділені комами) (отримайте тут {'@'}userinfobot) або (використовуйте команду '/id' у боті) Щоб надсилати в тему форуму, додайте ':topicId', наприклад -1001234567890:42",
|
||||||
"telegramNotifyTime": "Час сповіщення",
|
"telegramNotifyTime": "Час сповіщення",
|
||||||
"telegramNotifyTimeDesc": "Як часто бот Telegram надсилає періодичні звіти. Виберіть готовий інтервал або «Власний», щоб ввести вираз crontab.",
|
"telegramNotifyTimeDesc": "Як часто бот Telegram надсилає періодичні звіти. Виберіть готовий інтервал або «Власний», щоб ввести вираз crontab.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Máy chủ API Telegram",
|
"telegramAPIServer": "Máy chủ API Telegram",
|
||||||
"telegramAPIServerDesc": "Máy chủ API Telegram để sử dụng. Để trống để sử dụng máy chủ mặc định.",
|
"telegramAPIServerDesc": "Máy chủ API Telegram để sử dụng. Để trống để sử dụng máy chủ mặc định.",
|
||||||
"telegramChatId": "Chat ID Telegram của quản trị viên",
|
"telegramChatId": "Chat ID Telegram của quản trị viên",
|
||||||
"telegramChatIdDesc": "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng {'@'}userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn.",
|
"telegramChatIdDesc": "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng {'@'}userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn. Để gửi vào một chủ đề diễn đàn, thêm ':topicId', ví dụ -1001234567890:42",
|
||||||
"telegramNotifyTime": "Thời gian thông báo của bot Telegram",
|
"telegramNotifyTime": "Thời gian thông báo của bot Telegram",
|
||||||
"telegramNotifyTimeDesc": "Tần suất bot Telegram gửi báo cáo định kỳ. Chọn một khoảng thời gian có sẵn, hoặc chọn Tùy chỉnh để nhập biểu thức crontab.",
|
"telegramNotifyTimeDesc": "Tần suất bot Telegram gửi báo cáo định kỳ. Chọn một khoảng thời gian có sẵn, hoặc chọn Tùy chỉnh để nhập biểu thức crontab.",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Telegram API 服务器",
|
"telegramAPIServer": "Telegram API 服务器",
|
||||||
"telegramAPIServerDesc": "要使用的 Telegram API 服务器。留空以使用默认服务器。",
|
"telegramAPIServerDesc": "要使用的 Telegram API 服务器。留空以使用默认服务器。",
|
||||||
"telegramChatId": "管理员聊天 ID",
|
"telegramChatId": "管理员聊天 ID",
|
||||||
"telegramChatIdDesc": "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 {'@'}userinfobot 获取,或在机器人中使用 '/id' 命令获取)",
|
"telegramChatIdDesc": "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 {'@'}userinfobot 获取,或在机器人中使用 '/id' 命令获取) 如需发送到论坛话题,请追加 ':topicId',例如 -1001234567890:42",
|
||||||
"telegramNotifyTime": "通知时间",
|
"telegramNotifyTime": "通知时间",
|
||||||
"telegramNotifyTimeDesc": "Telegram 机器人发送周期性报告的频率。选择预设间隔,或选择“自定义”以输入 crontab 表达式。",
|
"telegramNotifyTimeDesc": "Telegram 机器人发送周期性报告的频率。选择预设间隔,或选择“自定义”以输入 crontab 表达式。",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"telegramAPIServer": "Telegram API 伺服器",
|
"telegramAPIServer": "Telegram API 伺服器",
|
||||||
"telegramAPIServerDesc": "要使用的 Telegram API 伺服器。留空以使用預設伺服器。",
|
"telegramAPIServerDesc": "要使用的 Telegram API 伺服器。留空以使用預設伺服器。",
|
||||||
"telegramChatId": "管理員聊天 ID",
|
"telegramChatId": "管理員聊天 ID",
|
||||||
"telegramChatIdDesc": "Telegram 管理員聊天 ID (多個以逗號分隔)(可通過 {'@'}userinfobot 獲取,或在機器人中使用 '/id' 命令獲取)",
|
"telegramChatIdDesc": "Telegram 管理員聊天 ID (多個以逗號分隔)(可通過 {'@'}userinfobot 獲取,或在機器人中使用 '/id' 命令獲取) 如需發送到論壇話題,請追加 ':topicId',例如 -1001234567890:42",
|
||||||
"telegramNotifyTime": "通知時間",
|
"telegramNotifyTime": "通知時間",
|
||||||
"telegramNotifyTimeDesc": "Telegram 機器人傳送週期性報告的頻率。選擇預設間隔,或選擇「自訂」以輸入 crontab 運算式。",
|
"telegramNotifyTimeDesc": "Telegram 機器人傳送週期性報告的頻率。選擇預設間隔,或選擇「自訂」以輸入 crontab 運算式。",
|
||||||
"notifyTime": {
|
"notifyTime": {
|
||||||
|
|||||||
Reference in New Issue
Block a user