Spaces:
Paused
Paused
| package db | |
| import ( | |
| "context" | |
| "crypto/aes" | |
| "crypto/cipher" | |
| "crypto/rand" | |
| "encoding/base64" | |
| "fmt" | |
| "io" | |
| "time" | |
| "go.mongodb.org/mongo-driver/bson" | |
| "go.mongodb.org/mongo-driver/mongo" | |
| "go.mongodb.org/mongo-driver/mongo/options" | |
| "go.uber.org/zap" | |
| ) | |
| // UserBotManager handles user bot operations | |
| type UserBotManager struct { | |
| collection *mongo.Collection | |
| log *zap.Logger | |
| encryptionKey []byte // 32 bytes for AES-256 | |
| } | |
| // NewUserBotManager creates a new UserBotManager instance | |
| func NewUserBotManager(database *mongo.Database, log *zap.Logger, encryptionKey string) *UserBotManager { | |
| // Ensure encryption key is 32 bytes for AES-256 | |
| key := make([]byte, 32) | |
| copy(key, []byte(encryptionKey)) | |
| return &UserBotManager{ | |
| collection: database.Collection("user_bots"), | |
| log: log.Named("UserBotManager"), | |
| encryptionKey: key, | |
| } | |
| } | |
| // encryptToken encrypts a bot token using AES-256-GCM | |
| func (ubm *UserBotManager) encryptToken(token string) (string, error) { | |
| block, err := aes.NewCipher(ubm.encryptionKey) | |
| if err != nil { | |
| return "", err | |
| } | |
| gcm, err := cipher.NewGCM(block) | |
| if err != nil { | |
| return "", err | |
| } | |
| nonce := make([]byte, gcm.NonceSize()) | |
| if _, err := io.ReadFull(rand.Reader, nonce); err != nil { | |
| return "", err | |
| } | |
| ciphertext := gcm.Seal(nonce, nonce, []byte(token), nil) | |
| return base64.StdEncoding.EncodeToString(ciphertext), nil | |
| } | |
| // decryptToken decrypts a bot token | |
| func (ubm *UserBotManager) decryptToken(encryptedToken string) (string, error) { | |
| data, err := base64.StdEncoding.DecodeString(encryptedToken) | |
| if err != nil { | |
| return "", err | |
| } | |
| block, err := aes.NewCipher(ubm.encryptionKey) | |
| if err != nil { | |
| return "", err | |
| } | |
| gcm, err := cipher.NewGCM(block) | |
| if err != nil { | |
| return "", err | |
| } | |
| nonceSize := gcm.NonceSize() | |
| if len(data) < nonceSize { | |
| return "", fmt.Errorf("ciphertext too short") | |
| } | |
| nonce, ciphertext := data[:nonceSize], data[nonceSize:] | |
| plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) | |
| if err != nil { | |
| return "", err | |
| } | |
| return string(plaintext), nil | |
| } | |
| // AddUserBot adds or updates a user's bot configuration | |
| func (ubm *UserBotManager) AddUserBot(ctx context.Context, userID int64, botToken, botUsername string, botID int64) error { | |
| // Encrypt the bot token | |
| encryptedToken, err := ubm.encryptToken(botToken) | |
| if err != nil { | |
| return fmt.Errorf("failed to encrypt bot token: %w", err) | |
| } | |
| userBot := UserBot{ | |
| UserID: userID, | |
| BotToken: encryptedToken, | |
| BotUsername: botUsername, | |
| BotID: botID, | |
| IsActive: true, | |
| IsVerified: true, // Assume verified if we got here | |
| LastUsed: time.Now(), | |
| ErrorCount: 0, | |
| CreatedAt: time.Now(), | |
| UpdatedAt: time.Now(), | |
| } | |
| // Upsert the user bot (replace if exists) | |
| opts := options.Replace().SetUpsert(true) | |
| _, err = ubm.collection.ReplaceOne( | |
| ctx, | |
| bson.M{"user_id": userID}, | |
| userBot, | |
| opts, | |
| ) | |
| if err != nil { | |
| return fmt.Errorf("failed to add user bot: %w", err) | |
| } | |
| ubm.log.Info("Added user bot", | |
| zap.Int64("userID", userID), | |
| zap.String("botUsername", botUsername), | |
| zap.Int64("botID", botID)) | |
| return nil | |
| } | |
| // GetUserBot retrieves a user's bot configuration | |
| func (ubm *UserBotManager) GetUserBot(ctx context.Context, userID int64) (*UserBot, error) { | |
| var userBot UserBot | |
| err := ubm.collection.FindOne(ctx, bson.M{"user_id": userID}).Decode(&userBot) | |
| if err != nil { | |
| if err == mongo.ErrNoDocuments { | |
| return nil, nil // No bot configured | |
| } | |
| return nil, fmt.Errorf("failed to get user bot: %w", err) | |
| } | |
| return &userBot, nil | |
| } | |
| // GetDecryptedBotToken retrieves and decrypts a user's bot token | |
| func (ubm *UserBotManager) GetDecryptedBotToken(ctx context.Context, userID int64) (string, error) { | |
| userBot, err := ubm.GetUserBot(ctx, userID) | |
| if err != nil { | |
| return "", err | |
| } | |
| if userBot == nil { | |
| return "", fmt.Errorf("no bot configured for user") | |
| } | |
| if !userBot.IsActive { | |
| return "", fmt.Errorf("user bot is disabled") | |
| } | |
| token, err := ubm.decryptToken(userBot.BotToken) | |
| if err != nil { | |
| return "", fmt.Errorf("failed to decrypt bot token: %w", err) | |
| } | |
| return token, nil | |
| } | |
| // UpdateBotUsage updates the last used time and resets error count on successful use | |
| func (ubm *UserBotManager) UpdateBotUsage(ctx context.Context, userID int64) error { | |
| update := bson.M{ | |
| "$set": bson.M{ | |
| "last_used": time.Now(), | |
| "updated_at": time.Now(), | |
| "error_count": 0, | |
| "last_error": "", | |
| }, | |
| } | |
| _, err := ubm.collection.UpdateOne( | |
| ctx, | |
| bson.M{"user_id": userID}, | |
| update, | |
| ) | |
| return err | |
| } | |
| // RecordBotError records an error for a user's bot | |
| func (ubm *UserBotManager) RecordBotError(ctx context.Context, userID int64, errorMsg string) error { | |
| update := bson.M{ | |
| "$inc": bson.M{"error_count": 1}, | |
| "$set": bson.M{ | |
| "last_error": errorMsg, | |
| "updated_at": time.Now(), | |
| }, | |
| } | |
| // Disable bot if too many consecutive errors | |
| result, err := ubm.collection.UpdateOne( | |
| ctx, | |
| bson.M{"user_id": userID}, | |
| update, | |
| ) | |
| if err != nil { | |
| return err | |
| } | |
| // Check if we need to disable the bot due to too many errors | |
| if result.ModifiedCount > 0 { | |
| userBot, err := ubm.GetUserBot(ctx, userID) | |
| if err == nil && userBot != nil && userBot.ErrorCount >= 5 { | |
| // Disable bot after 5 consecutive errors | |
| ubm.DisableUserBot(ctx, userID) | |
| ubm.log.Warn("Disabled user bot due to too many errors", | |
| zap.Int64("userID", userID), | |
| zap.String("lastError", errorMsg)) | |
| } | |
| } | |
| return nil | |
| } | |
| // DisableUserBot disables a user's bot | |
| func (ubm *UserBotManager) DisableUserBot(ctx context.Context, userID int64) error { | |
| update := bson.M{ | |
| "$set": bson.M{ | |
| "is_active": false, | |
| "updated_at": time.Now(), | |
| }, | |
| } | |
| _, err := ubm.collection.UpdateOne( | |
| ctx, | |
| bson.M{"user_id": userID}, | |
| update, | |
| ) | |
| return err | |
| } | |
| // EnableUserBot enables a user's bot | |
| func (ubm *UserBotManager) EnableUserBot(ctx context.Context, userID int64) error { | |
| update := bson.M{ | |
| "$set": bson.M{ | |
| "is_active": true, | |
| "error_count": 0, | |
| "last_error": "", | |
| "updated_at": time.Now(), | |
| }, | |
| } | |
| _, err := ubm.collection.UpdateOne( | |
| ctx, | |
| bson.M{"user_id": userID}, | |
| update, | |
| ) | |
| return err | |
| } | |
| // RemoveUserBot removes a user's bot configuration | |
| func (ubm *UserBotManager) RemoveUserBot(ctx context.Context, userID int64) error { | |
| _, err := ubm.collection.DeleteOne(ctx, bson.M{"user_id": userID}) | |
| if err != nil { | |
| return fmt.Errorf("failed to remove user bot: %w", err) | |
| } | |
| ubm.log.Info("Removed user bot", zap.Int64("userID", userID)) | |
| return nil | |
| } | |
| // ListActiveBots returns all active user bots (for admin purposes) | |
| func (ubm *UserBotManager) ListActiveBots(ctx context.Context) ([]UserBot, error) { | |
| cursor, err := ubm.collection.Find(ctx, bson.M{"is_active": true}) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer cursor.Close(ctx) | |
| var bots []UserBot | |
| if err := cursor.All(ctx, &bots); err != nil { | |
| return nil, err | |
| } | |
| return bots, nil | |
| } |