package rpc import ( "unicode/utf8" "github.com/iamxvbaba/td/tg" ) const ( richMessageLengthLimit = 32768 richMessageMaxBlocks = 500 richMessageMaxDepth = 16 richMessageMaxMedia = 50 richMessageMaxTableCols = 20 ) type richMessageMetrics struct { textLength int blocks int depth int media int tableCols int } func validateRichMessageBlocks(blocks []tg.PageBlockClass) error { metrics := richMessageMetrics{} collectRichMessageBlockMetrics(blocks, 1, &metrics) if metrics.textLength > richMessageLengthLimit || metrics.blocks > richMessageMaxBlocks || metrics.depth > richMessageMaxDepth || metrics.media > richMessageMaxMedia || metrics.tableCols > richMessageMaxTableCols { return richMessageTooLongErr() } if metrics.blocks == 0 || metrics.textLength == 0 && metrics.media == 0 { return richMessageInvalidErr() } return nil } func collectRichMessageBlockMetrics(blocks []tg.PageBlockClass, depth int, metrics *richMessageMetrics) { if depth > metrics.depth { metrics.depth = depth } for _, block := range blocks { metrics.blocks++ switch value := block.(type) { case *tg.PageBlockTitle: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockSubtitle: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockHeader: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockSubheader: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockKicker: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockParagraph: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockPreformatted: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockFooter: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockHeading1: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockHeading2: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockHeading3: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockHeading4: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockHeading5: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockHeading6: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockMath: metrics.textLength += utf16StringLength(value.Source) case *tg.PageBlockThinking: metrics.textLength += richTextUTF16Length(value.Text) case *tg.PageBlockAuthorDate: metrics.textLength += richTextUTF16Length(value.Author) case *tg.PageBlockBlockquote: metrics.textLength += richTextUTF16Length(value.Text) + richTextUTF16Length(value.Caption) case *tg.PageBlockPullquote: metrics.textLength += richTextUTF16Length(value.Text) + richTextUTF16Length(value.Caption) case *tg.PageBlockBlockquoteBlocks: metrics.textLength += richTextUTF16Length(value.Caption) collectRichMessageBlockMetrics(value.Blocks, depth+1, metrics) case *tg.PageBlockDetails: metrics.textLength += richTextUTF16Length(value.Title) collectRichMessageBlockMetrics(value.Blocks, depth+1, metrics) case *tg.PageBlockList: for _, item := range value.Items { switch item := item.(type) { case *tg.PageListItemText: metrics.textLength += richTextUTF16Length(item.Text) case *tg.PageListItemBlocks: collectRichMessageBlockMetrics(item.Blocks, depth+1, metrics) } } case *tg.PageBlockOrderedList: for _, item := range value.Items { switch item := item.(type) { case *tg.PageListOrderedItemText: metrics.textLength += richTextUTF16Length(item.Text) case *tg.PageListOrderedItemBlocks: collectRichMessageBlockMetrics(item.Blocks, depth+1, metrics) } } case *tg.PageBlockTable: metrics.textLength += richTextUTF16Length(value.Title) for _, row := range value.Rows { columns := 0 for _, cell := range row.Cells { metrics.textLength += richTextUTF16Length(cell.Text) if cell.Colspan > 1 { columns += cell.Colspan } else { columns++ } } if columns > metrics.tableCols { metrics.tableCols = columns } } case *tg.PageBlockCollage: metrics.textLength += richTextUTF16Length(value.Caption.Text) + richTextUTF16Length(value.Caption.Credit) collectRichMessageBlockMetrics(value.Items, depth+1, metrics) case *tg.PageBlockSlideshow: metrics.textLength += richTextUTF16Length(value.Caption.Text) + richTextUTF16Length(value.Caption.Credit) collectRichMessageBlockMetrics(value.Items, depth+1, metrics) case *tg.PageBlockCover: collectRichMessageBlockMetrics([]tg.PageBlockClass{value.Cover}, depth+1, metrics) case *tg.PageBlockEmbed: if id, ok := value.GetPosterPhotoID(); ok && id != 0 { metrics.media++ } case *tg.PageBlockEmbedPost: if value.AuthorPhotoID != 0 { metrics.media++ } collectRichMessageBlockMetrics(value.Blocks, depth+1, metrics) case *tg.PageBlockRelatedArticles: for i := range value.Articles { if id, ok := value.Articles[i].GetPhotoID(); ok && id != 0 { metrics.media++ } } case *tg.PageBlockPhoto, *tg.PageBlockVideo, *tg.PageBlockAudio: metrics.media++ } } } func richTextUTF16Length(text tg.RichTextClass) int { switch value := text.(type) { case nil, *tg.TextEmpty: return 0 case *tg.TextPlain: return utf16StringLength(value.Text) case *tg.TextConcat: total := 0 for _, child := range value.Texts { total += richTextUTF16Length(child) } return total case *tg.TextBold: return richTextUTF16Length(value.Text) case *tg.TextItalic: return richTextUTF16Length(value.Text) case *tg.TextUnderline: return richTextUTF16Length(value.Text) case *tg.TextStrike: return richTextUTF16Length(value.Text) case *tg.TextFixed: return richTextUTF16Length(value.Text) case *tg.TextSubscript: return richTextUTF16Length(value.Text) case *tg.TextSuperscript: return richTextUTF16Length(value.Text) case *tg.TextMarked: return richTextUTF16Length(value.Text) case *tg.TextSpoiler: return richTextUTF16Length(value.Text) case *tg.TextURL: return richTextUTF16Length(value.Text) case *tg.TextMention: return richTextUTF16Length(value.Text) case *tg.TextHashtag: return richTextUTF16Length(value.Text) case *tg.TextBotCommand: return richTextUTF16Length(value.Text) case *tg.TextCashtag: return richTextUTF16Length(value.Text) case *tg.TextAutoURL: return richTextUTF16Length(value.Text) case *tg.TextAutoEmail: return richTextUTF16Length(value.Text) case *tg.TextAutoPhone: return richTextUTF16Length(value.Text) case *tg.TextBankCard: return richTextUTF16Length(value.Text) case *tg.TextEmail: return richTextUTF16Length(value.Text) case *tg.TextPhone: return richTextUTF16Length(value.Text) case *tg.TextAnchor: return richTextUTF16Length(value.Text) case *tg.TextMentionName: return richTextUTF16Length(value.Text) case *tg.TextDate: return richTextUTF16Length(value.Text) case *tg.TextCustomEmoji: return utf16StringLength(value.Alt) case *tg.TextMath: return utf16StringLength(value.Source) default: return 0 } } func utf16StringLength(value string) int { length := 0 for _, r := range value { length++ if r > utf8.RuneSelf && r > 0xffff { length++ } } return length }