Skip to content

Commit

Permalink
Add MarkdownV2 support (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dolfik1 authored Apr 23, 2024
1 parent 9a619e8 commit b678e57
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/Funogram.Generator/Types/TypesGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type ParseMode =
| Markdown
/// Html parse syntax
| HTML
/// MarkdownV2 parse syntax
| [<DataMember(Name = "MarkdownV2")>] MarkdownV2
/// Type of action to broadcast
type ChatAction =
Expand Down
2 changes: 1 addition & 1 deletion src/Funogram.Telegram/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>7.2.0</Version>
<Version>7.2.0.1</Version>
<Authors>Nikolay Matyushin</Authors>
<Product>Funogram.Telegram</Product>
<Title>Funogram.Telegram</Title>
Expand Down
2 changes: 2 additions & 0 deletions src/Funogram.Telegram/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type ParseMode =
| Markdown
/// Html parse syntax
| HTML
/// MarkdownV2 parse syntax
| [<DataMember(Name = "MarkdownV2")>] MarkdownV2

/// Type of action to broadcast
type ChatAction =
Expand Down
3 changes: 3 additions & 0 deletions src/Funogram.Tests/Constants.fs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ module Constants =
}: Req.ForwardMessage) :> IBotRequest
let jsonForwardMessageReq = """{"chat_id":"Dolfik","from_chat_id":10,"message_id":10}"""

let sendMessageReq = Req.SendMessage.Make(ChatId.String "Dolfik", "Hello, world", parseMode = ParseMode.MarkdownV2) :> IBotRequest
let jsonSendMessageReq = """{"chat_id":"Dolfik","text":"Hello, world","parse_mode":"MarkdownV2"}"""

let jsonTestObjChatMember = ChatMemberMember.Create("member", User.Create(600000000L, false, "firstName", "lastName", "userName", "ru"))
let jsonTestObjChatMemberResultString = """{"ok":true,"result":{"status":"member","user":{"id":600000000,"is_bot":false,"first_name":"firstName","last_name":"lastName","username":"userName","language_code":"ru"}}}"""

8 changes: 7 additions & 1 deletion src/Funogram.Tests/Json.fs
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,10 @@ let ``JSON deserializing ChatMember``() =
|> parseJson
|> function
| Ok result -> shouldEqual result Constants.jsonTestObjChatMember
| Error error -> failwith error.Description
| Error error -> failwith error.Description

[<Fact>]
let ``JSON serializing send message request`` () =
Constants.sendMessageReq
|> toJsonBotRequestString
|> shouldEqual Constants.jsonSendMessageReq
15 changes: 2 additions & 13 deletions src/Funogram/Converters.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ open System
open System.Collections.Generic
open System.IO
open System.Runtime.CompilerServices
open System.Runtime.Serialization
open System.Text.Json
open System.Text.Json.Serialization
open TypeShape.Core
Expand All @@ -25,8 +24,8 @@ module internal Converters =
&& (case.Fields[1].Member.Type = typeof<Stream> || case.Fields[1].Member.Type = typeof<byte[]>)

if case.Fields.Length = 0 then
let name = caseName case.CaseInfo
fun (writer: Utf8JsonWriter) _ _ ->
let name = toSnakeCase case.CaseInfo.Name
writer.WriteStringValue(name)
else
case.Fields[0].Accept { new IMemberVisitor<'DeclaringType, Utf8JsonWriter -> 'DeclaringType -> JsonSerializerOptions -> unit> with
Expand Down Expand Up @@ -89,17 +88,7 @@ module internal Converters =
union.UnionCases
|> Seq.map (fun c ->
if c.Fields.Length = 0 then
let dataMember =
c.CaseInfo.GetCustomAttributes(typeof<DataMemberAttribute>)
|> Seq.cast<DataMemberAttribute>
|> Seq.filter (fun x -> String.IsNullOrEmpty(x.Name) |> not)
|> Seq.toArray

let name =
if dataMember.Length > 0
then dataMember[0].Name
else c.CaseInfo.Name |> toSnakeCase
(Set.ofList [name], None)
(Set.ofList [caseName c.CaseInfo], None)
else
let tp = c.Fields[0].Member.Type
if tp.IsPrimitive then (Set.empty, Some tp)
Expand Down
2 changes: 1 addition & 1 deletion src/Funogram/Funogram.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>3.0.0</VersionPrefix>
<VersionPrefix>3.0.1</VersionPrefix>
<Authors>Nikolay Matyushin</Authors>
<Product>Funogram</Product>
<Description>Funogram is a functional Telegram Bot Api library for F#</Description>
Expand Down
15 changes: 14 additions & 1 deletion src/Funogram/StringUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module Funogram.StringUtils
open System
open System.Linq.Expressions
open System.Reflection
open System.Runtime.Serialization
open Microsoft.FSharp.Reflection

let toSnakeCase =
let assembly = Assembly.Load("System.Text.Json")
Expand All @@ -21,4 +23,15 @@ let toSnakeCase =

// not good solution, but fastest
let fn = createConvertNameFunc instance
fn.Invoke
fn.Invoke

let inline caseName (caseInfo: UnionCaseInfo) =
let dataMember =
caseInfo.GetCustomAttributes(typeof<DataMemberAttribute>)
|> Seq.cast<DataMemberAttribute>
|> Seq.filter (fun x -> String.IsNullOrEmpty(x.Name) |> not)
|> Seq.toArray

if dataMember.Length > 0
then dataMember[0].Name
else caseInfo.Name |> toSnakeCase
4 changes: 2 additions & 2 deletions src/Funogram/Tools.fs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ module Api =
else None

if isEnum then
let name = StringUtils.toSnakeCase case.CaseInfo.Name
let name = StringUtils.caseName case.CaseInfo
fun _ (prop: string) (data: MultipartFormDataContent) ->
data.Add(strf "%s" name, prop) $ true
else
Expand Down Expand Up @@ -422,7 +422,7 @@ module Api =

use content = new MultipartFormDataContent()
let hasData = serialize request content

let! result =
if hasData then client.PostAsync(url, content) |> Async.AwaitTask
else client.GetAsync(url) |> Async.AwaitTask
Expand Down
2 changes: 2 additions & 0 deletions src/examples/Funogram.TestBot/Commands/Base.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let defaultText = """⭐️Available test commands:
/send_message7 - Test inline keyboard
/send_message8 - Test multiple media
/send_message9 - Test multiple media as bytes
/send_message10 - MarkdownV2 test
/send_action - Test action
Expand Down Expand Up @@ -46,6 +47,7 @@ let updateArrived (ctx: UpdateContext) =

cmd "/send_message8" (fun _ -> Files.testUploadAndSendPhotoGroup |> wrap)
cmd "/send_message9" (fun _ -> Files.testUploadAndSendPhotoGroupAsBytes |> wrap)
cmd "/send_message10" (fun _ -> TextMessages.testMarkdownV2 |> wrap)

cmd "/forward_message" (fun _ -> TextMessages.testForwardMessage ctx |> wrap)

Expand Down
58 changes: 56 additions & 2 deletions src/examples/Funogram.TestBot/Commands/TextMessages.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,62 @@ open Funogram.Telegram.Types
let private sendMessageFormatted text parseMode config chatId =
Req.SendMessage.Make(ChatId.Int chatId, text, parseMode = parseMode) |> bot config

let testMarkdown = sendMessageFormatted "Test *Markdown*" ParseMode.Markdown
let testHtml = sendMessageFormatted "Test <b>HTML</b>" ParseMode.HTML
[<Literal>]
let MarkdownExample = """*bold text*
_italic text_
[inline URL](http://www.example.com/)
[inline mention of a user](tg://user?id=123456789)
`inline fixed-width code`
```
pre-formatted fixed-width code block
```
```python
pre-formatted fixed-width code block written in the Python programming language
```"""

[<Literal>]
let MarkdownV2Example = """*bold \*text*
_italic \*text_
__underline__
~strikethrough~
||spoiler||
*bold _italic bold ~italic bold strikethrough ||italic bold strikethrough spoiler||~ __underline italic bold___ bold*
[inline URL](http://www.example.com/)
[inline mention of a user](tg://user?id=123456789)
![👍](tg://emoji?id=5368324170671202286)
`inline fixed-width code`
```
pre-formatted fixed-width code block
```
```python
pre-formatted fixed-width code block written in the Python programming language
```
>Block quotation started
>Block quotation continued
>The last line of the block quotation**
>The second block quotation started right after the previous\r
>The third block quotation started right after the previous"""

[<Literal>]
let HtmlExample = """
<b>bold</b>, <strong>bold</strong>
<i>italic</i>, <em>italic</em>
<u>underline</u>, <ins>underline</ins>
<s>strikethrough</s>, <strike>strikethrough</strike>, <del>strikethrough</del>
<span class="tg-spoiler">spoiler</span>, <tg-spoiler>spoiler</tg-spoiler>
<b>bold <i>italic bold <s>italic bold strikethrough <span class="tg-spoiler">italic bold strikethrough spoiler</span></s> <u>underline italic bold</u></i> bold</b>
<a href="http://www.example.com/">inline URL</a>
<a href="tg://user?id=123456789">inline mention of a user</a>
<tg-emoji emoji-id="5368324170671202286">👍</tg-emoji>
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width code block</pre>
<pre><code class="language-python">pre-formatted fixed-width code block written in the Python programming language</code></pre>
<blockquote>Block quotation started\nBlock quotation continued\nThe last line of the block quotation</blockquote>
"""

let testMarkdown = sendMessageFormatted MarkdownExample ParseMode.Markdown
let testMarkdownV2 = sendMessageFormatted MarkdownV2Example ParseMode.MarkdownV2
let testHtml = sendMessageFormatted HtmlExample ParseMode.HTML
let testNoWebpageAndNotification config chatId =
Req.SendMessage.Make(
ChatId.Int chatId,
Expand Down

0 comments on commit b678e57

Please sign in to comment.