Skip to content

Commit a1e1842

Browse files
committed
(#188) Telegram: support new quotes
1 parent 292f58b commit a1e1842

2 files changed

Lines changed: 117 additions & 34 deletions

File tree

Emulsion.Telegram/Funogram.fs

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ module MessageConverter =
157157
|> addAuthorIfAvailable
158158
|> markAsQuote quoteSettings.linePrefix
159159

160-
let private getAuthoredMessageBodyText (message: FunogramMessage) links =
160+
let private getAuthoredMessageBodyText (message: FunogramMessage) (quote: TextQuote option) links =
161161
let (|Text|_|) (message: FunogramMessage) = message.Text
162162
let (|Poll|_|) (message: FunogramMessage) = message.Poll
163163
let (|Content|_|) (message: FunogramMessage) =
@@ -190,22 +190,25 @@ module MessageConverter =
190190
| _ -> $"{text}: {addedLinks}"
191191

192192
let text =
193-
match message with
194-
| Text text -> applyEntities message.Entities text
195-
| Content (contentType, caption) ->
196-
let contentInfo =
197-
match caption with
198-
| Some caption ->
199-
let text = applyEntities message.CaptionEntities caption
200-
$"[{contentType} with caption \"{text}\"]"
201-
| None ->
202-
$"[{contentType}]"
203-
appendLinkTo contentInfo
204-
| Poll poll ->
205-
let text = getPollText poll
206-
$"[Poll] {text}"
207-
| _ ->
208-
appendLinkTo "[DATA UNRECOGNIZED]"
193+
match quote with
194+
| Some quote -> quote.Text
195+
| None ->
196+
match message with
197+
| Text text -> applyEntities message.Entities text
198+
| Content (contentType, caption) ->
199+
let contentInfo =
200+
match caption with
201+
| Some caption ->
202+
let text = applyEntities message.CaptionEntities caption
203+
$"[{contentType} with caption \"{text}\"]"
204+
| None ->
205+
$"[{contentType}]"
206+
appendLinkTo contentInfo
207+
| Poll poll ->
208+
let text = getPollText poll
209+
$"[Poll] {text}"
210+
| _ ->
211+
appendLinkTo "[DATA UNRECOGNIZED]"
209212

210213
match message with
211214
| ForwardFrom author ->
@@ -253,7 +256,7 @@ module MessageConverter =
253256
Authored { author = author; text = text }
254257
| Event _ as msg -> msg
255258

256-
let private isSelfMessage selfUserId (message: FunogramMessage) =
259+
let private isBotOwnMessage selfUserId (message: FunogramMessage) =
257260
match message.From with
258261
| Some user -> user.Id = selfUserId
259262
| None -> false
@@ -263,7 +266,7 @@ module MessageConverter =
263266
then Some message
264267
else None
265268

266-
let private extractMessageContent(message: FunogramMessage) links: Message =
269+
let private extractMessageContent(message: FunogramMessage) quote links: Message =
267270
match message with
268271
| EventFunogramMessage msg ->
269272
Event { text = getEventMessageBodyText msg }
@@ -272,40 +275,64 @@ module MessageConverter =
272275
message.From
273276
|> Option.map getUserDisplayName
274277
|> Option.defaultValue "[UNKNOWN USER]"
275-
let mainBody = getAuthoredMessageBodyText message links
278+
let mainBody = getAuthoredMessageBodyText message quote links
276279
Authored { author = mainAuthor; text = mainBody }
277280

278281
/// For messages from the bot, the first bold section of the message will contain the nickname of the author.
279282
/// Everything else is the message text.
280-
let private extractSelfMessageContent(message: FunogramMessage) link: Message =
283+
let private extractBotOwnMessageContent(message: FunogramMessage) (quote: TextQuote option) link: Message =
284+
let splitMessageText (boldEntity: MessageEntity) (text: string) =
285+
let authorNameOffset = Math.Clamp(int32 boldEntity.Offset, 0, text.Length)
286+
let authorNameLength = Math.Clamp(int32 boldEntity.Length, 0, text.Length - authorNameOffset)
287+
let authorName = text.Substring(authorNameOffset, authorNameLength)
288+
289+
let messageTextOffset = Math.Clamp(
290+
authorNameOffset + authorNameLength + 1, // +1 for \n
291+
0,
292+
text.Length
293+
)
294+
let messageText = text.Substring messageTextOffset
295+
authorName, messageText
296+
281297
match (message.Entities, message.Text) with
282-
| None, _ | _, None -> extractMessageContent message link
298+
| None, _ | _, None -> extractMessageContent message quote link
283299
| Some entities, Some text ->
284-
let boldEntity = Seq.tryFind (fun (e: MessageEntity) -> e.Type = "bold") entities
300+
let boldEntity = Array.tryFind (fun (e: MessageEntity) -> e.Type = "bold") entities
285301
match boldEntity with
286-
| None -> extractMessageContent message link
302+
| None -> extractMessageContent message quote link
287303
| Some section ->
288-
let authorNameOffset = Math.Clamp(int32 section.Offset, 0, text.Length)
289-
let authorNameLength = Math.Clamp(int32 section.Length, 0, text.Length - authorNameOffset)
290-
let authorName = text.Substring(authorNameOffset, authorNameLength)
291-
let messageTextOffset = Math.Clamp(authorNameOffset + authorNameLength + 1, 0, text.Length) // +1 for \n
292-
let messageText = text.Substring messageTextOffset
293-
Authored { author = authorName; text = messageText }
304+
// Always read the author message from the original text, not the quoted part (in case it's inaccurate).
305+
let authorName, messageText = splitMessageText section text
306+
match quote with
307+
| None -> // No quote: read the rest of the text from the main message.
308+
Authored { author = authorName; text = messageText }
309+
| Some quote ->
310+
// In case the quote has a prt of the author name, then drop that part and use the full name
311+
// instead.
312+
let entities = quote.Entities |> Option.defaultValue Array.empty
313+
let boldPartOfTheQuote = Array.tryFind (fun (e: MessageEntity) -> e.Type = "bold") entities
314+
let fullQuoteText = quote.Text
315+
match boldPartOfTheQuote with
316+
| None -> // The quote doesn't include the original author name.
317+
Authored { author = authorName; text = fullQuoteText }
318+
| Some section -> // The quote includes (a part of) the original author name; ignore it.
319+
let _, quotedText = splitMessageText section fullQuoteText
320+
Authored { author = authorName; text = quotedText }
294321

295322
let (|ForumTopicCreatedMessage|_|) (m: FunogramMessage option) =
296323
match m with
297324
| Some m when Option.isSome m.ForumTopicCreated -> Some m
298325
| _ -> None
299326

300327
let internal read (selfUserId: int64) (message: FunogramMessage, links: TelegramThreadLinks): ThreadMessage =
301-
let mainMessage = extractMessageContent message links.ContentLinks
328+
let mainMessage = extractMessageContent message None links.ContentLinks
302329
match message.ReplyToMessage with
303330
| None | ForumTopicCreatedMessage _ -> { main = mainMessage; replyTo = None }
304331
| Some replyTo ->
305332
let replyToMessage =
306-
if isSelfMessage selfUserId replyTo
307-
then extractSelfMessageContent replyTo links.ReplyToContentLinks
308-
else extractMessageContent replyTo links.ReplyToContentLinks
333+
if isBotOwnMessage selfUserId replyTo
334+
then extractBotOwnMessageContent replyTo message.Quote links.ReplyToContentLinks
335+
else extractMessageContent replyTo message.Quote links.ReplyToContentLinks
309336
{ main = mainMessage; replyTo = Some replyToMessage }
310337

311338
let internal processSendResult(result: Result<'a, ApiResponseError>): 'a =

Emulsion.Tests/Telegram/FunogramTests.fs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,62 @@ module ReadMessageTests =
580580
readMessage replyMessage
581581
)
582582

583+
[<Fact>]
584+
let ``Partial quote is properly preserved``() =
585+
let originalMessage = {
586+
defaultMessage with
587+
From = Some originalUser
588+
Text = Some "foo bar baz"
589+
}
590+
let replyMessage = {
591+
defaultMessage with
592+
From = Some replyingUser
593+
ReplyToMessage = Some originalMessage
594+
Quote = Some {
595+
Entities = None
596+
Position = 3
597+
Text = "bar"
598+
IsManual = None
599+
}
600+
Text = Some "Reply text"
601+
}
602+
603+
Assert.Equal(
604+
authoredTelegramReplyMessage "@replyingUser" "Reply text"
605+
(authoredTelegramMessage "@originalUser" "bar").main,
606+
readMessage replyMessage
607+
)
608+
609+
[<Fact>]
610+
let ``Partial quote in own message is properly preserved``() =
611+
let replyMessage = {
612+
defaultMessage with
613+
From = Some replyingUser
614+
ReplyToMessage = Some {
615+
defaultMessage with
616+
From = Some <| User.Create(selfUserId, isBot = true, firstName = "")
617+
Text = Some "xmppUser\nfoo bar"
618+
Entities = Some [|
619+
createEntity "bold" 0 "xmppUser".Length ""
620+
|]
621+
}
622+
Quote = Some {
623+
Entities = Some [|
624+
createEntity "bold" 0 "ppUser".Length ""
625+
|]
626+
Position = 2
627+
Text = "ppUser\nfoo"
628+
IsManual = Some true
629+
}
630+
Text = Some "Reply text"
631+
}
632+
633+
Assert.Equal(
634+
authoredTelegramReplyMessage "@replyingUser" "Reply text"
635+
(authoredTelegramMessage "xmppUser" "foo").main,
636+
readMessage replyMessage
637+
)
638+
583639
module ProcessMessageTests =
584640
let private processMessageOpt o =
585641
processMessage Logger.None None None o

0 commit comments

Comments
 (0)