package integram import ( "errors" "fmt" "reflect" "regexp" "strconv" "strings" "sync" "time" tg "github.com/requilence/telegram-bot-api" log "github.com/sirupsen/logrus" mgo "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" ) var updateMapMutex = &sync.RWMutex{} var updateMutexPerBotPerChat = make(map[string]*sync.Mutex) type msgInfo struct { TS time.Time ID int InlineID string BotID int64 ChatID int64 } func updateRoutine(b *Bot, u *tg.Update) { if !Config.Debug { defer func() { if r := recover(); r != nil { stack := stack(3) log.Errorf("Panic recovery at updateRoutine -> %s\n%s\n", r, stack) } }() } updateReceivedAt := time.Now() var chatID int64 if u.Message != nil { chatID = u.Message.Chat.ID } else if u.CallbackQuery != nil { if u.CallbackQuery.Message != nil { chatID = u.CallbackQuery.Message.Chat.ID } else { chatID = u.CallbackQuery.From.ID } } else if u.EditedMessage != nil { chatID = u.EditedMessage.Chat.ID } else if u.ChosenInlineResult != nil { chatID = u.ChosenInlineResult.From.ID } else if u.InlineQuery != nil { chatID = u.InlineQuery.From.ID } mutexID := fmt.Sprintf("%d_%d", b.ID, chatID) updateMapMutex.Lock() m, exists := updateMutexPerBotPerChat[mutexID] updateMapMutex.Unlock() if exists { m.Lock() } else { m = &sync.Mutex{} m.Lock() updateMapMutex.Lock() updateMutexPerBotPerChat[mutexID] = m updateMapMutex.Unlock() } db := mongoSession.Clone().DB(mongo.Database) defer func() { m.Unlock() defer func() { if r := recover(); r != nil { fmt.Println(r) } }() db.Session.Close() }() service, context := tgUpdateHandler(u, b, db) if service == nil || context == nil { return } if context.Message != nil && !context.MessageEdited { replyActionProcessed := false if context.Message.ReplyToMessage != nil { rm := context.Message.ReplyToMessage log.Debugf("Received reply for message %d", rm.MsgID) // TODO: detect service by ReplyHandler if rm.OnReplyAction != "" { log.Debugf("ReplyHandler found %s", rm.OnReplyAction) if handler, ok := actionFuncs[service.trimFuncPath(rm.OnReplyAction)]; ok { handlerType := reflect.TypeOf(handler) log.Debugf("handler %v: %v %v\n", rm.OnReplyAction, handlerType.String(), handlerType.Kind().String()) handlerArgsInterfaces := make([]interface{}, handlerType.NumIn()-1) handlerArgs := make([]reflect.Value, handlerType.NumIn()) for i := 1; i < handlerType.NumIn(); i++ { dataVal := reflect.New(handlerType.In(i)) handlerArgsInterfaces[i-1] = dataVal.Interface() } if err := decode(rm.OnReplyData, &handlerArgsInterfaces); err != nil { log.WithField("handler", rm.OnReplyAction).WithError(err).Error("Can't decode replyHandler's args") } handlerArgs[0] = reflect.ValueOf(context) for i := 0; i < len(handlerArgsInterfaces); i++ { handlerArgs[i+1] = reflect.ValueOf(handlerArgsInterfaces[i]) } if len(handlerArgs) > 0 { handlerVal := reflect.ValueOf(handler) returnVals := handlerVal.Call(handlerArgs) if !returnVals[0].IsNil() { err := returnVals[0].Interface().(error) // NOTE: panics will be caught by the recover statement above log.WithField("handler", rm.OnReplyAction).WithError(err).Error("replyHandler failed") } replyActionProcessed = true } } else { log.WithField("handler", rm.OnReplyAction).Error("Reply handler not registered") } } } if !replyActionProcessed { if service.TGNewMessageHandler == nil { context.Log().Warn("Received Message but TGNewMessageHandler not set for service") return } err := service.TGNewMessageHandler(context) if err != nil { context.Log().WithError(err).Error("BotUpdateHandler error") } } // Save incoming message metadata(text and files are excluded) in case it has onReply/onEdit actions or have associated event if context.Message.OnEditAction != "" || context.Message.OnReplyAction != "" || len(context.Message.EventID) > 0 { err := context.Message.Message.saveToDB(db) if err != nil { log.WithError(err).Error("can't add incoming message to db") } } if context.messageAnsweredAt != nil { context.StatIncChat(StatIncomingMessageAnswered) } else { context.StatIncChat(StatIncomingMessageNotAnswered) } } else if context.InlineQuery != nil { if service.TGInlineQueryHandler == nil { context.Log().Warn("Received InlineQuery but TGInlineQueryHandler not set for service") return } queryHandlerStarted := time.Now() err := service.TGInlineQueryHandler(context) if err != nil { if strings.Contains(err.Error(), "QUERY_ID_INVALID") { context.StatIncUser(StatInlineQueryTimeouted) } else if !strings.Contains(err.Error(), "context canceled") { context.StatIncUser(StatInlineQueryCanceled) } context.Log().WithError(err).WithField("secSpent", time.Now().Sub(queryHandlerStarted).Seconds()).WithField("secSpentSinceUpdate", time.Now().Sub(updateReceivedAt).Seconds()).Error("BotUpdateHandler InlineQuery error") context.StatIncUser(StatInlineQueryProcessingError) } else { if context.inlineQueryAnsweredAt == nil { context.StatIncUser(StatInlineQueryNotAnswered) context.Log().WithError(err).Error("BotUpdateHandler InlineQuery not answered") } else { context.StatIncUser(StatInlineQueryAnswered) secsSpent := context.inlineQueryAnsweredAt.Sub(updateReceivedAt).Seconds() if secsSpent > 10 { context.Log().WithError(err).Errorf("BotUpdateHandler InlineQuery 10 sec exceeded: %.1f sec spent after update, %.1f sec after the handle", secsSpent, time.Now().Sub(queryHandlerStarted).Seconds()) } } } return } else if context.ChosenInlineResult != nil { if service.TGChosenInlineResultHandler == nil { context.Log().Warn("Received ChosenInlineResult but TGChosenInlineResultHandler not set for service") return } err := service.TGChosenInlineResultHandler(context) context.StatIncUser(StatInlineQueryChosen) if err != nil { context.Log().WithError(err).Error("BotUpdateHandler error") } return } // If this message is keyboard answer - remove that keyboard from user/chat if context.Message.ReplyToMessage != nil && context.Message.ReplyToMessage.om != nil && context.Message.ReplyToMessage.om.OneTimeKeyboard { key, _ := context.KeyboardAnswer() if key != "" { var err error // if received reply is result of button pressed // TODO: fix for non-selective one_time_keyboard in group chat _, err = db.C("users").UpdateAll(bson.M{}, bson.M{"$pull": bson.M{"keyboardperchat": bson.M{"chatid": context.Chat.ID, "msgid": context.Message.ReplyToMessage.ID}}}) if err != nil { log.WithError(err).Debugf("can't remove onetime keyboard from user") } if context.Chat.IsGroup() { err = db.C("chats").Update(bson.M{"_id": context.Chat.ID, "keyboard.msgid": context.Message.ReplyToMessage.ID}, bson.M{"$unset": bson.M{"keyboard": true}}) } else { _, err = db.C("chats").UpdateAll(bson.M{"_id": context.Chat.ID}, bson.M{"$pull": bson.M{"keyboardperbot": bson.M{"botid": context.Message.BotID, "msgid": context.Message.ReplyToMessage.ID}}}) } if err != nil { log.WithError(err).Debugf("can't remove onetime keyboard from chat") } } } } func (bot *Bot) listen() { api := bot.API if bot.updatesChan == nil { var err error bot.updatesChan, err = api.GetUpdatesChan(tg.UpdateConfig{Timeout: randomInRange(10, 20), Limit: 100}) if err != nil { log.WithField("bot", bot.ID).WithError(err).Panic("Can't get updatesChan") } } go func(c <-chan tg.Update, b *Bot) { var context Context defer func() { fmt.Println("Stop listening for updates on bot " + b.Username) if r := recover(); r != nil { stack := stack(3) context.Log().Errorf("Panic recovery at updatesChan -> %s\n%s\n", r, stack) tgUpdatesRevoltChan <- b } }() for { u := <-c go updateRoutine(b, &u) } }(bot.updatesChan, bot) } func tgUserPointer(u *tg.User) *User { if u == nil { return nil } return &User{ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Lang: u.LanguageCode} } func tgChatPointer(c *tg.Chat) *Chat { if c == nil { return nil } return &Chat{ID: c.ID, FirstName: c.FirstName, LastName: c.LastName, UserName: c.UserName, Title: c.Title, Type: c.Type} } func tgUser(u *tg.User) User { if u == nil { return User{} } return User{ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Lang: u.LanguageCode} } func tgChat(chat *tg.Chat) Chat { if chat == nil { return Chat{} } return Chat{ID: chat.ID, Title: chat.Title, Type: chat.Type, FirstName: chat.FirstName, LastName: chat.LastName, UserName: chat.UserName} } func incomingMessageFromTGMessage(m *tg.Message) IncomingMessage { im := IncomingMessage{} // Base message struct im.MsgID = m.MessageID if m.From != nil { im.FromID = m.From.ID } im.InlineMsgID = m.InlineMessageID im.ChatID = m.Chat.ID im.Date = time.Unix(int64(m.Date), 0) im.Text = m.Text if m.From != nil { im.From = tgUser(m.From) } im.Chat = Chat{ID: m.Chat.ID, Type: m.Chat.Type, FirstName: m.Chat.FirstName, LastName: m.Chat.LastName, UserName: m.Chat.UserName, Title: m.Chat.Title} im.ForwardFrom = tgUserPointer(m.ForwardFrom) im.ForwardFromChat = tgChatPointer(m.ForwardFromChat) im.ForwardDate = time.Unix(int64(m.ForwardDate), 0) im.ForwardFromMessageID = int64(m.ForwardFromMessageID) if m.ReplyToMessage != nil { rm := m.ReplyToMessage im.ReplyToMessage = &Message{MsgID: rm.MessageID, Date: time.Unix(int64(rm.Date), 0), Text: rm.Text} if rm.Chat != nil { im.ReplyToMessage.ChatID = rm.Chat.ID } if rm.From != nil { im.ReplyToMessage.FromID = rm.From.ID } } im.Caption = m.Caption if m.NewChatMembers != nil { for _, newMember := range *m.NewChatMembers { n := newMember im.NewChatMembers = append(im.NewChatMembers, tgUserPointer(&n)) } } im.LeftChatMember = tgUserPointer(m.LeftChatMember) im.Audio = m.Audio im.Document = m.Document im.Photo = m.Photo im.Sticker = m.Sticker im.Video = m.Video im.Voice = m.Voice im.Contact = m.Contact im.Location = m.Location im.NewChatTitle = m.NewChatTitle im.NewChatPhoto = m.NewChatPhoto im.DeleteChatPhoto = m.DeleteChatPhoto im.GroupChatCreated = m.GroupChatCreated return im } func tgCallbackHandler(u *tg.Update, b *Bot, db *mgo.Database) (*Service, *Context) { var rm *Message var err error if u.CallbackQuery.Message != nil { rm, err = findMessage(db, u.CallbackQuery.Message.Chat.ID, b.ID, u.CallbackQuery.Message.MessageID) if err != nil { log.WithError(err).WithField("bot_id", b.ID).WithField("msg_id", u.CallbackQuery.Message.MessageID).Error("tgCallbackHandler can't find source message") } } else { rm, err = findInlineMessage(db, b.ID, u.CallbackQuery.InlineMessageID) if err != nil { log.WithError(err).WithField("bot_id", b.ID).WithField("msg_id", u.CallbackQuery.InlineMessageID).Error("tgCallbackHandler can't find source message") } } if rm != nil { service, err := detectServiceByBot(b.ID) if err != nil { log.WithError(err).WithField("bot", b.ID).Error("Can't detect service") } cbData := u.CallbackQuery.Data cbState := 0 if []byte(cbData)[0] == inlineButtonStateKeyword { log.Debug("INLINE_BUTTON_STATE_KEYWORD found") cbState, err = strconv.Atoi(cbData[1:2]) cbData = cbData[2:] if err != nil { log.WithError(err).Errorf("INLINE_BUTTON_STATE_KEYWORD found but next symbol is %s", cbData[1:2]) } } ctx := &Context{ db: db, ServiceName: service.Name, User: tgUser(u.CallbackQuery.From), Callback: &callback{ID: u.CallbackQuery.ID, Data: cbData, Message: rm.om, State: cbState}} var chat Chat if u.CallbackQuery.InlineMessageID != "" && rm.ChatID != 0 { chatData, err := ctx.FindChat(bson.M{"_id": rm.ChatID}) if err != nil { ctx.Log().WithError(err).WithField("chatID", rm.ChatID).Error("find chat for inline msg' callback") chat = Chat{ID: rm.ChatID} } else { chat = chatData.Chat } } else if u.CallbackQuery.Message != nil { chat = tgChat(u.CallbackQuery.Message.Chat) } else { chat = tgChat(&tg.Chat{ID: u.CallbackQuery.From.ID, LastName: u.CallbackQuery.From.LastName, UserName: u.CallbackQuery.From.UserName, Type: "private", FirstName: u.CallbackQuery.From.FirstName}) } ctx.Chat = chat ctx.User.ctx = ctx ctx.Chat.ctx = ctx if rm.OnCallbackAction != "" { log.Debugf("CallbackAction found %s", rm.OnCallbackAction) // Instantiate a new variable to hold this argument if handler, ok := actionFuncs[service.trimFuncPath(rm.OnCallbackAction)]; ok { handlerType := reflect.TypeOf(handler) log.Debugf("handler %v: %v %v\n", rm.OnCallbackAction, handlerType.String(), handlerType.Kind().String()) handlerArgsInterfaces := make([]interface{}, handlerType.NumIn()-1) handlerArgs := make([]reflect.Value, handlerType.NumIn()) for i := 1; i < handlerType.NumIn(); i++ { dataVal := reflect.New(handlerType.In(i)) handlerArgsInterfaces[i-1] = dataVal.Interface() } if err := decode(rm.OnCallbackData, &handlerArgsInterfaces); err != nil { ctx.Log().WithField("handler", rm.OnCallbackAction).WithError(err).Error("Can't decode replyHandler's args") } handlerArgs[0] = reflect.ValueOf(ctx) for i := 0; i < len(handlerArgsInterfaces); i++ { handlerArgs[i+1] = reflect.ValueOf(handlerArgsInterfaces[i]) } if len(handlerArgs) > 0 { handlerVal := reflect.ValueOf(handler) returnVals := handlerVal.Call(handlerArgs) if !returnVals[0].IsNil() { err := returnVals[0].Interface().(error) // NOTE: panics will be caught by the recover statement above ctx.Log().WithField("handler", rm.OnCallbackAction).WithError(err).Error("callbackAction failed") ctx.AnswerCallbackQuery("Oops! Please try again", false) } else { if ctx.Callback.AnsweredAt == nil { ctx.AnswerCallbackQuery("", false) } } } } else { ctx.Log().WithField("handler", rm.OnCallbackAction).Error("Callback handler not registered") } } return nil, ctx } return nil, nil } func tgInlineQueryHandler(u *tg.Update, b *Bot, db *mgo.Database) (*Service, *Context) { service, err := detectServiceByBot(b.ID) if err != nil { log.WithError(err).WithField("bot", b.ID).Error("Can't detect service") } user := tgUser(u.InlineQuery.From) ctx := &Context{ServiceName: service.Name, User: user, db: db, InlineQuery: u.InlineQuery} ctx.User.ctx = ctx return service, ctx } func tgChosenInlineResultHandler(u *tg.Update, b *Bot, db *mgo.Database) (*Service, *Context) { service, err := detectServiceByBot(b.ID) if err != nil { log.WithError(err).WithField("bot", b.ID).Error("Can't detect service") } user := tgUser(u.ChosenInlineResult.From) ctx := &Context{ServiceName: service.Name, User: user, db: db, ChosenInlineResult: &chosenInlineResult{ChosenInlineResult: *u.ChosenInlineResult}} ctx.User.ctx = ctx if u.Message != nil { // in case we corellated chosen update and chat message ctx.Chat = tgChat(u.Message.Chat) } ctx.Chat.ctx = ctx /*chatID:=0 for _,hook:=range ctx.User.data.Hooks{ if SliceContainsString(hook.Services,service.Name) { for _, chat := range hook.Chats { } } }*/ log.Debug("InlineMessageID: ", u.ChosenInlineResult.InlineMessageID) msg := OutgoingMessage{ ParseMode: "HTML", WebPreview: true, Message: Message{ ID: bson.NewObjectId(), InlineMsgID: u.ChosenInlineResult.InlineMessageID, Text: u.ChosenInlineResult.Query, // Todo: thats a lie. The actual message content is known while producing inline results FromID: u.ChosenInlineResult.From.ID, BotID: b.ID, ChatID: ctx.Chat.ID, Date: time.Now(), }} if u.Message != nil { msg.MsgID = u.Message.MessageID } // we need to save this message! err = db.C("messages").Insert(&msg) if err != nil { ctx.Log().WithError(err).Error("tgChosenInlineResultHandler: msg insert") } ctx.ChosenInlineResult.Message = &msg ctx.Log().WithField("message", &msg).Debug("tgChosenInlineResultHandler") return service, ctx } func tgEditedMessageHandler(u *tg.Update, b *Bot, db *mgo.Database) (*Service, *Context) { im := incomingMessageFromTGMessage(u.EditedMessage) im.BotID = b.ID service, err := detectServiceByBot(b.ID) if err != nil { log.WithError(err).WithField("bot", b.ID).Error("Can't detect service") } ctx := &Context{ServiceName: service.Name, Chat: im.Chat, db: db} if im.From.ID != 0 { ctx.User = im.From ctx.User.ctx = ctx } ctx.Chat.ctx = ctx ctx.Message = &im ctx.MessageEdited = true rm, _ := findMessage(db, im.ChatID, b.ID, im.MsgID) if rm != nil { log.Debugf("Received edit for message %d", rm.MsgID) if rm.OnEditAction != "" { log.Debugf("onEditHandler found %s", rm.OnEditAction) // Instantiate a new variable to hold this argument if handler, ok := actionFuncs[service.trimFuncPath(rm.OnEditAction)]; ok { handlerType := reflect.TypeOf(handler) log.Debugf("handler %v: %v %v\n", rm.OnEditAction, handlerType.String(), handlerType.Kind().String()) handlerArgsInterfaces := make([]interface{}, handlerType.NumIn()-1) handlerArgs := make([]reflect.Value, handlerType.NumIn()) for i := 1; i < handlerType.NumIn(); i++ { dataVal := reflect.New(handlerType.In(i)) handlerArgsInterfaces[i-1] = dataVal.Interface() } if err := decode(rm.OnEditData, &handlerArgsInterfaces); err != nil { log.WithField("handler", rm.OnEditAction).WithError(err).Error("Can't decode editHandler's args") } handlerArgs[0] = reflect.ValueOf(ctx) for i := 0; i < len(handlerArgsInterfaces); i++ { handlerArgs[i+1] = reflect.ValueOf(handlerArgsInterfaces[i]) } if len(handlerArgs) > 0 { handlerVal := reflect.ValueOf(handler) returnVals := handlerVal.Call(handlerArgs) if !returnVals[0].IsNil() { err := returnVals[0].Interface().(error) // NOTE: panics will be caught by the recover statement above log.WithField("handler", rm.OnEditAction).WithError(err).Error("editHandler failed") } } } else { log.WithField("handler", rm.OnEditAction).Error("Edit handler not registered") } } } return service, ctx } func tgIncomingMessageHandler(u *tg.Update, b *Bot, db *mgo.Database) (*Service, *Context) { im := incomingMessageFromTGMessage(u.Message) im.BotID = b.ID service, err := detectServiceByBot(b.ID) if err != nil { log.WithError(err).WithField("bot", b.ID).Error("Can't detect service") } ctx := &Context{ServiceName: service.Name, Chat: im.Chat, db: db} if im.From.ID != 0 { ctx.User = im.From ctx.User.ctx = ctx } ctx.Chat.ctx = ctx var rm *Message if im.ReplyToMessage != nil && im.ReplyToMessage.MsgID != 0 { rm, _ = findMessage(db, im.ReplyToMessage.ChatID, b.ID, im.ReplyToMessage.MsgID) im.ReplyToMessage = rm if rm != nil { im.ReplyToMessage.BotID = b.ID im.ReplyToMsgID = im.ReplyToMessage.MsgID } } else if im.Chat.IsPrivate() { // For private chat all messages is reply for the last message. We need to parse message for /commands first cmd, _ := im.GetCommand() if cmd == "" { // If there is active keyboard – received message is reply for the source message kb, _ := ctx.keyboard() if kb.MsgID > 0 { rm, err = findMessage(db, im.Chat.ID, b.ID, kb.MsgID) if rm == nil { ctx.Log().WithError(err).WithField("msgid", kb.MsgID).WithField("botid", b.ID).Error("Keyboard message source not found") } } if rm == nil { rm, err = findLastMessageInChat(db, b.ID, im.ChatID) if err != nil && err.Error() != "not found" { ctx.Log().WithError(err).Error("Error on findLastOutgoingMessageInChat") } else if rm != nil { // assume user's message as the reply for the last message sent by the bot if bot's message has Reply handler and ForceReply set if rm.FromID != rm.BotID || !rm.om.ForceReply || rm.om.OnReplyAction == "" { rm = nil } } } // Leave ReplyToMessage empty to avoid unnecessary db request in case we don't need prev message. im.ReplyToMessage = rm if rm != nil { im.ReplyToMessage.BotID = b.ID im.ReplyToMsgID = im.ReplyToMessage.MsgID } } } ctx.Message = &im // unset botstoppedorkickedat in case it was previosly set // E.g. bot was stopped by user and now restarted // Or it was kicked from a group chat and now it is invited again if ctx != nil && ctx.Message != nil { key := "protected." + ctx.ServiceName + ".botstoppedorkickedat" db.C("chats").Update(bson.M{"_id": ctx.Chat.ID, key: bson.M{"$exists": true}}, bson.M{"$unset": bson.M{key: ""}}) } return service, ctx } func removeHooksForChat(db *mgo.Database, serviceName string, chatID int64) { err := db.C("users").Update(bson.M{"hooks.services": []string{serviceName}, "hooks.chats": chatID}, bson.M{"$pull": bson.M{"hooks.$.chats": chatID}}) if err != nil { if err != nil { log.WithError(err).Error("removeHooksForChat remove outdated hook chats") } } } func migrateToSuperGroup(db *mgo.Database, fromChatID int64, toChatID int64) { if fromChatID == toChatID { return } var chat chatData _, err := db.C("chats").FindId(fromChatID).Apply(mgo.Change{ Update: bson.M{"$set": bson.M{"migratedtochatid": toChatID}, "$unset": bson.M{"hooks": "", "membersids": ""}}, }, &chat) if err != nil { log.WithError(err).Error("migrateToSuperGroup remove") } if chat.ID != 0 { chat.ID = toChatID chat.Type = "supergroup" chat.MigratedFromChatID = toChatID _, err := db.C("chats").Upsert(bson.M{"_id": toChatID, "migratedfromchatid": bson.M{"$exists": false}}, chat) if err != nil { log.WithError(err).Error("migrateToSuperGroup ID insert error") } } err = db.C("users").Update(bson.M{"hooks.chats": fromChatID}, bson.M{"$addToSet": bson.M{"hooks.$.chats": toChatID}}) if err != nil { log.WithError(err).Error("migrateToSuperGroup add new hook chats") } err = db.C("users").Update(bson.M{"hooks.chats": toChatID}, bson.M{"$pull": bson.M{"hooks.$.chats": fromChatID}}) if err != nil { log.WithError(err).Error("migrateToSuperGroup remove outdated hook chats") } } func tgUpdateHandler(u *tg.Update, b *Bot, db *mgo.Database) (*Service, *Context) { if u.Message != nil && u.ChosenInlineResult == nil { if u.Message.LeftChatMember != nil { db.C("chats").UpdateId(u.Message.Chat.ID, bson.M{"$pull": bson.M{"membersids": u.Message.From.ID}}) return nil, nil } else if u.Message.MigrateToChatID != 0 { log.Infof("Group %v migrated to supergroup %v", u.Message.Chat.ID, u.Message.MigrateToChatID) migrateToSuperGroup(db, u.Message.Chat.ID, u.Message.MigrateToChatID) return nil, nil } return tgIncomingMessageHandler(u, b, db) } else if u.CallbackQuery != nil { if u.CallbackQuery.Message != nil && (u.CallbackQuery.Message.Chat.IsGroup() || u.CallbackQuery.Message.Chat.IsSuperGroup()) { db.C("chats").UpdateId(u.CallbackQuery.Message.Chat.ID, bson.M{"$addToSet": bson.M{"membersids": u.CallbackQuery.From.ID}}) } return tgCallbackHandler(u, b, db) } else if u.InlineQuery != nil { return tgInlineQueryHandler(u, b, db) } else if u.ChosenInlineResult != nil { return tgChosenInlineResultHandler(u, b, db) } else if u.EditedMessage != nil { return tgEditedMessageHandler(u, b, db) } else if u.ChannelPost != nil { u.Message = u.ChannelPost return tgIncomingMessageHandler(u, b, db) } else if u.EditedChannelPost != nil { u.EditedMessage = u.EditedChannelPost return tgEditedMessageHandler(u, b, db) } return nil, nil } /*func (im *IncomingMessage) ButtonAnswer(db *mgo.Database) (key string, text string) { if im.Message.ReplyToMsgID == 0 { return } om, err := im.ReplyToMessage.FindOutgoingMessage(db) if err != nil || om == nil { return } if val, ok := om.Buttons[im.Text]; ok { return val, im.Text } return }*/ // saveToDB stores incoming message metadata to the database func (m *Message) saveToDB(db *mgo.Database) error { // text is excluded, instead saving textHash m.TextHash = m.GetTextHash() return db.C("messages").Insert(m) } // IsEventBotAddedToGroup returns true if user created a new group with bot as member or add the bot to existing group func (m *IncomingMessage) IsEventBotAddedToGroup() bool { if (len(m.NewChatMembers) > 0 && m.NewChatMembers[0].ID == m.BotID) || m.GroupChatCreated || m.SuperGroupChatCreated { return true } return false } // GetCommand parses received message text for bot command. Returns the command and after command text if presented func (m *IncomingMessage) GetCommand() (string, string) { text := m.Text if !strings.HasPrefix(text, "/") { return "", text } r, _ := regexp.Compile("^/([a-zA-Z0-9_]+)(?:@[a-zA-Z0-9_]+)?.?(.*)?$") match := r.FindStringSubmatch(text) if len(match) == 3 { return match[1], match[2] } else if len(match) == 2 { return match[1], "" } return "", "" } func detectServiceByBot(botID int64) (*Service, error) { serviceName := "" if botID > 0 { if bot := botByID(botID); bot != nil { if len(bot.services) == 1 { serviceName = bot.services[0].Name } } } if serviceName == "" { return &Service{}, fmt.Errorf("Can't determine active service for bot with ID=%d. No messages found.", botID) } if val, ok := services[serviceName]; ok { return val, nil } return &Service{}, errors.New("Unknown service: " + serviceName) } func (m *Message) detectService(db *mgo.Database) (*Service, error) { serviceName := "" if m.BotID > 0 { if bot := botByID(m.BotID); bot != nil { if len(bot.services) == 1 { serviceName = bot.services[0].Name } } } if serviceName == "" { return &Service{}, fmt.Errorf("Can't determine active service for bot with ID=%d. No messages found.", m.BotID) } if val, ok := services[serviceName]; ok { return val, nil } return &Service{}, errors.New("Unknown service: " + serviceName) }