Add user creation functionality and update commands in main.go and README.md
Publish telemt-bot Docker image / build-and-push (push) Successful in 52s
Publish telemt-bot Docker image / build-and-push (push) Successful in 52s
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
- `/health` -> `GET /v1/health`
|
||||
- `/summary` -> `GET /v1/stats/summary`
|
||||
- `/users` -> `GET /v1/stats/users` (первые 20 строк)
|
||||
- `/create_user <username>` -> `POST /v1/users` (создание пользователя + ссылки на подключение)
|
||||
|
||||
Команды обрабатываются только от одного админ-чата (`TELEGRAM_ADMIN_CHAT_ID`).
|
||||
|
||||
|
||||
@@ -87,6 +87,22 @@ type userInfo struct {
|
||||
CurrentConnections uint64 `json:"current_connections"`
|
||||
ActiveUniqueIPs uint64 `json:"active_unique_ips"`
|
||||
TotalOctets uint64 `json:"total_octets"`
|
||||
Links userLinks `json:"links"`
|
||||
}
|
||||
|
||||
type userLinks struct {
|
||||
Classic []string `json:"classic"`
|
||||
Secure []string `json:"secure"`
|
||||
TLS []string `json:"tls"`
|
||||
}
|
||||
|
||||
type createUserRequest struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type createUserResponse struct {
|
||||
User userInfo `json:"user"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -253,13 +269,15 @@ func (b *bot) handleMessage(ctx context.Context, msg *tgMessage) error {
|
||||
}
|
||||
switch command {
|
||||
case "/start", "/help":
|
||||
return b.sendMessage(ctx, chatID, "Команды:\n/health\n/summary\n/users")
|
||||
return b.sendMessage(ctx, chatID, "Команды:\n/health\n/summary\n/users\n/create_user <username>")
|
||||
case "/health":
|
||||
return b.handleHealth(ctx, chatID)
|
||||
case "/summary":
|
||||
return b.handleSummary(ctx, chatID)
|
||||
case "/users":
|
||||
return b.handleUsers(ctx, chatID)
|
||||
case "/create_user", "/createuser":
|
||||
return b.handleCreateUser(ctx, chatID, text)
|
||||
default:
|
||||
return b.sendMessage(ctx, chatID, "Неизвестная команда. Используй /help")
|
||||
}
|
||||
@@ -315,14 +333,91 @@ func (b *bot) handleUsers(ctx context.Context, chatID int64) error {
|
||||
return b.sendMessage(ctx, chatID, strings.TrimRight(sb.String(), "\n"))
|
||||
}
|
||||
|
||||
func (b *bot) handleCreateUser(ctx context.Context, chatID int64, text string) error {
|
||||
parts := strings.Fields(text)
|
||||
if len(parts) < 2 {
|
||||
return b.sendMessage(ctx, chatID, "Использование: /create_user <username>")
|
||||
}
|
||||
|
||||
username := strings.TrimSpace(parts[1])
|
||||
if !isValidUsername(username) {
|
||||
return b.sendMessage(ctx, chatID, "Некорректный username. Разрешены [A-Za-z0-9_.-], длина 1..64.")
|
||||
}
|
||||
|
||||
req := createUserRequest{Username: username}
|
||||
var created createUserResponse
|
||||
if err := b.callTelemtJSON(ctx, http.MethodPost, "/v1/users", req, &created, http.StatusCreated); err != nil {
|
||||
return b.sendMessage(ctx, chatID, "Ошибка создания пользователя: "+err.Error())
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Пользователь создан: %s\nsecret: %s", created.User.Username, created.Secret)
|
||||
if linksText := formatUserLinks(created.User.Links); linksText != "" {
|
||||
msg = msg + "\n\nСсылки:\n" + linksText
|
||||
}
|
||||
return b.sendMessage(ctx, chatID, msg)
|
||||
}
|
||||
|
||||
func formatUserLinks(links userLinks) string {
|
||||
var sb strings.Builder
|
||||
appendLinks := func(title string, items []string) {
|
||||
if len(items) == 0 {
|
||||
return
|
||||
}
|
||||
sb.WriteString(title)
|
||||
sb.WriteByte('\n')
|
||||
for i := range items {
|
||||
sb.WriteString("- ")
|
||||
sb.WriteString(items[i])
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
appendLinks("classic:", links.Classic)
|
||||
appendLinks("secure:", links.Secure)
|
||||
appendLinks("tls:", links.TLS)
|
||||
return strings.TrimRight(sb.String(), "\n")
|
||||
}
|
||||
|
||||
func isValidUsername(v string) bool {
|
||||
if len(v) < 1 || len(v) > 64 {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(v); i++ {
|
||||
ch := v[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == '-' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *bot) callTelemt(ctx context.Context, path string, out any) error {
|
||||
return b.callTelemtJSON(ctx, http.MethodGet, path, nil, out, http.StatusOK)
|
||||
}
|
||||
|
||||
func (b *bot) callTelemtJSON(ctx context.Context, method, path string, payload any, out any, successStatus int) error {
|
||||
cctx, cancel := context.WithTimeout(ctx, b.cfg.TelemtTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(cctx, http.MethodGet, b.cfg.TelemtAPIBaseURL+path, nil)
|
||||
var bodyReader *bytes.Reader
|
||||
if payload != nil {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bodyReader = bytes.NewReader(body)
|
||||
} else {
|
||||
bodyReader = bytes.NewReader(nil)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(cctx, method, b.cfg.TelemtAPIBaseURL+path, bodyReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if payload != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
if b.cfg.TelemtAPIAuth != "" {
|
||||
req.Header.Set("Authorization", b.cfg.TelemtAPIAuth)
|
||||
}
|
||||
@@ -333,13 +428,19 @@ func (b *bot) callTelemt(ctx context.Context, path string, out any) error {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("telemt status %s", resp.Status)
|
||||
}
|
||||
|
||||
var env telemtEnvelope
|
||||
if err := json.NewDecoder(resp.Body).Decode(&env); err != nil {
|
||||
return err
|
||||
if resp.StatusCode != successStatus {
|
||||
return fmt.Errorf("telemt status %s", resp.Status)
|
||||
}
|
||||
return fmt.Errorf("telemt decode response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != successStatus {
|
||||
if env.Error != nil {
|
||||
return fmt.Errorf("%s: %s", env.Error.Code, env.Error.Message)
|
||||
}
|
||||
return fmt.Errorf("telemt status %s", resp.Status)
|
||||
}
|
||||
if !env.OK {
|
||||
if env.Error != nil {
|
||||
@@ -347,7 +448,10 @@ func (b *bot) callTelemt(ctx context.Context, path string, out any) error {
|
||||
}
|
||||
return errors.New("telemt returned ok=false")
|
||||
}
|
||||
if len(env.Data) == 0 {
|
||||
if out == nil {
|
||||
return nil
|
||||
}
|
||||
if len(env.Data) == 0 || string(env.Data) == "null" {
|
||||
return errors.New("telemt empty data")
|
||||
}
|
||||
return json.Unmarshal(env.Data, out)
|
||||
|
||||
Reference in New Issue
Block a user