// EditMessageTextAndInlineKeyboard edit the outgoing message's text and inline keyboard
502545
func (c *Context) EditMessageTextAndInlineKeyboard(om *OutgoingMessage, fromState string, text string, kb InlineKeyboard) error {
	bot := c.Bot()
	if om.MsgID != 0 {
		log.WithField("msgID", om.MsgID).Debug("EditMessageTextAndInlineKeyboard")
	} else {
		om.ChatID = 0
		log.WithField("inlineMsgID", om.InlineMsgID).Debug("EditMessageTextAndInlineKeyboard")
	}

	var msg OutgoingMessage

	var err error

	if om.ParseMode == "HTML" {
		textCleared, err := sanitize.HTMLAllowing(text, []string{"a", "b", "strong", "i", "em", "a", "code", "pre"}, []string{"href"})

		if err == nil && textCleared != "" {
			text = textCleared
		}
	}

	om.Text = text
	prevTextHash := om.TextHash
	om.TextHash = om.GetTextHash()

	if fromState != "" {
		_, err = c.db.C("messages").Find(bson.M{"_id": om.ID, "$or": []bson.M{{"inlinekeyboardmarkup.state": fromState}, {"inlinekeyboardmarkup": bson.M{"$exists": false}}}}).Apply(mgo.Change{Update: bson.M{"$set": bson.M{"inlinekeyboardmarkup": kb, "texthash": om.TextHash}}}, &msg)
	} else {
		_, err = c.db.C("messages").Find(bson.M{"_id": om.ID}).Apply(mgo.Change{Update: bson.M{"$set": bson.M{"inlinekeyboardmarkup": kb, "texthash": om.TextHash}}}, &msg)
	}

	if err != nil {
		c.Log().WithError(err).Error("EditMessageTextAndInlineKeyboard messages update error")
	}

	if msg.BotID == 0 {
		c.Log().Warn(fmt.Sprintf("EditMessageTextAndInlineKeyboard – message (_id=%s botid=%v id=%v state %s) not found", om.ID.Hex(), bot.ID, om.MsgID, fromState))
		return nil

	}

	tgKeyboard := kb.tg()

	if prevTextHash == om.TextHash {
		prevTGKeyboard := om.InlineKeyboardMarkup.tg()
		if whetherTGInlineKeyboardsAreEqual(prevTGKeyboard, tgKeyboard) {
			c.Log().Debugf("EditMessageTextAndInlineKeyboard – message (_id=%s botid=%v id=%v state %s) not updated both text and kb have not changed", om.ID.Hex(), bot.ID, om.MsgID, fromState)
			return nil
		}
	}


	_, err = bot.API.Send(tg.EditMessageTextConfig{
		BaseEdit: tg.BaseEdit{
			ChatID:          om.ChatID,
			InlineMessageID: om.InlineMsgID,
			MessageID:       om.MsgID,
			ReplyMarkup:     &tg.InlineKeyboardMarkup{InlineKeyboard: tgKeyboard},
		},
		ParseMode: om.ParseMode,
		Text:      text,
		DisableWebPagePreview: !om.WebPreview,
	})

	if err != nil {
		if err.(tg.Error).IsCantAccessChat() || err.(tg.Error).ChatMigrated() {
			if c.Callback != nil {
				c.AnswerCallbackQuery("Message can be outdated. Bot can't edit messages created before converting to the Super Group", false)
			}
		} else if err.(tg.Error).IsAntiFlood() {
			c.Log().WithError(err).Warn("TG Anti flood activated")
		}
		// Oops. error is occurred – revert the original keyboard
		c.db.C("messages").Update(bson.M{"_id": msg.ID}, bson.M{"$set": bson.M{"texthash": prevTextHash, "inlinekeyboardmarkup": msg.InlineKeyboardMarkup}})
		return err
	}

	return nil
}
1106910
func (c *Context) DeleteMessagesWithEventID(eventID string) (deleted int, err error) {
	var messages []OutgoingMessage
	f := bson.M{"botid": c.Bot().ID, "eventid": eventID}

	//update MAX_MSGS_TO_UPDATE_WITH_EVENTID last bot messages
	c.db.C("messages").Find(f).Sort("-_id").Limit(MaxMsgsToUpdateWithEventID).All(&messages)
	for _, message := range messages {
		err = c.DeleteMessage(&message)
		if err != nil {
			c.Log().WithError(err).WithField("eventid", eventID).Error("DeleteMessagesWithEventID")
		} else {
			deleted++
		}
	}
	return deleted, err
}
597459
func (api *Client) PostMessageContext(ctx context.Context, channelID string, options ...MsgOption) (string, string, error) {
	respChannel, respTimestamp, _, err := api.SendMessageContext(
		ctx,
		channelID,
		MsgOptionPost(),
		MsgOptionCompose(options...),
	)
	return respChannel, respTimestamp, err
}
349103
func (bot TgBot) SendMessageWithKeyboard(cid int, text string, parsemode *ParseModeT, dwp *bool, rtmid *int, rm ReplyKeyboardMarkup) ResultWithMessage {
	var rkm ReplyMarkupInt = rm
	return bot.SendMessage(cid, text, parsemode, dwp, rtmid, &rkm)
}
886237
func MarshalInternalMessage(m Message, s Serializer) ([]byte, error) {
	typ := getMessageType(m)
	buf, err := s.Marshal(m)
	if err != nil {
		return nil, errors.Wrap(err, "marshaling")
	}
	return append([]byte{typ}, buf...), nil
}
1083849
func (api *Client) UpdateMessage(channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
	return api.SendMessageContext(context.Background(), channelID, MsgOptionUpdate(timestamp), MsgOptionCompose(options...))
}
605355
func (m *OutgoingMessage) SetResizeKeyboard(b bool) *OutgoingMessage {
	m.ResizeKeyboard = b
	return m
}
914583
func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
	return api.SendMessageContext(ctx, channelID, MsgOptionUpdate(timestamp), MsgOptionCompose(options...))
}
384260
func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) {
	rtm.Debugln("Sending message:", msg)
	if len([]rune(msg.Text)) > MaxMessageTextLength {
		rtm.IncomingEvents <- RTMEvent{"outgoing_error", &MessageTooLongEvent{
			Message:   msg,
			MaxLength: MaxMessageTextLength,
		}}
		return
	}

	if err := rtm.sendWithDeadline(msg); err != nil {
		rtm.IncomingEvents <- RTMEvent{"outgoing_error", &OutgoingErrorEvent{
			Message:  msg,
			ErrorObj: err,
		}}
		// TODO force ping?
	}
}
189003
func (c *client) processInboundMsg(msg []byte) {
	switch c.kind {
	case CLIENT:
		c.processInboundClientMsg(msg)
	case ROUTER:
		c.processInboundRoutedMsg(msg)
	case GATEWAY:
		c.processInboundGatewayMsg(msg)
	case LEAF:
		c.processInboundLeafMsg(msg)
	}
}
629276
func (c *Client) MessageWithExtrasAndContext(ctx context.Context, level string, msg string, extras map[string]interface{}) {
	if !c.configuration.enabled {
		return
	}
	body := c.buildBody(ctx, level, msg, extras)
	data := body["data"].(map[string]interface{})
	data["body"] = messageBody(msg)
	c.push(body)
}
171559
func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig {
	return EditMessageCaptionConfig{
		BaseEdit: BaseEdit{
			ChatID:    chatID,
			MessageID: messageID,
		},
		Caption:   caption,
	}
}
138066
func MarshalMessageSet(m map[int32]Extension) ([]byte, error) {
	if err := encodeExtensionMap(m); err != nil {
		return nil, err
	}

	// Sort extension IDs to provide a deterministic encoding.
	// See also enc_map in encode.go.
	ids := make([]int, 0, len(m))
	for id := range m {
		ids = append(ids, int(id))
	}
	sort.Ints(ids)

	ms := &MessageSet{Item: make([]*_MessageSet_Item, 0, len(m))}
	for _, id := range ids {
		e := m[int32(id)]
		// Remove the wire type and field number varint, as well as the length varint.
		msg := skipVarint(skipVarint(e.enc))

		ms.Item = append(ms.Item, &_MessageSet_Item{
			TypeId:  Int32(int32(id)),
			Message: msg,
		})
	}
	return Marshal(ms)
}
343500
func UpsertMessageType(m *pb.Message) tag.Mutator {
	return tag.Upsert(KeyMessageType, m.Type.String())
}
627848
func getSenderPrefix(mvalid chat1.MessageUnboxedValid, conv chat1.ConversationLocal, currentUsername string) (senderPrefix string) {
	if showSenderPrefix(mvalid, conv) {
		sender := mvalid.SenderUsername
		if sender == currentUsername {
			senderPrefix = "You: "
		} else {
			senderPrefix = fmt.Sprintf("%s: ", sender)
		}
	}
	return senderPrefix
}
1232952
func MessageWithExtras(level string, msg string, extras map[string]interface{}) {
	std.MessageWithExtras(level, msg, extras)
}
48190
func (c *Context) EditMessagesWithEventID(eventID string, fromState string, text string, kb InlineKeyboard) (edited int, err error) {
	var messages []OutgoingMessage
	f := bson.M{"botid": c.Bot().ID, "eventid": eventID}
	if fromState != "" {
		f["inlinekeyboardmarkup.state"] = fromState
	}

	//update MAX_MSGS_TO_UPDATE_WITH_EVENTID last bot messages
	c.db.C("messages").Find(f).Sort("-_id").Limit(MaxMsgsToUpdateWithEventID).All(&messages)
	for _, message := range messages {
		err = c.EditMessageTextAndInlineKeyboard(&message, fromState, text, kb)
		if err != nil {
			c.Log().WithError(err).WithField("eventid", eventID).Error("EditMessagesWithEventID")
		} else {
			edited++
		}
	}
	return edited, err
}