Skip to content

Commit

Permalink
Fix unmarshall did not affect nested objects. Use custom pragama `t…
Browse files Browse the repository at this point in the history
…elebotInternalUse` instead of `type` dirty hack. BREAKING CHANGE! ref #67
  • Loading branch information
ba0f3 committed Jan 7, 2022
1 parent afd2a21 commit 858a750
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 61 deletions.
4 changes: 2 additions & 2 deletions examples/share_contact.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var L = newConsoleLogger(fmtStr = "$levelname, [$time] ")
addHandler(L)

func singleReply*(btn: KeyboardButton): ReplyKeyboardMarkup =
ReplyKeyboardMarkup(`type`: kReplyKeyboardMarkup, keyboard: @[@[btn]])
ReplyKeyboardMarkup(kind: kReplyKeyboardMarkup, keyboard: @[@[btn]])

const API_KEY = slurp("secret.key").strip()

Expand All @@ -23,7 +23,7 @@ proc updateHandler(b: Telebot, u: Update): Future[bool] {.gcsafe, async.} =
response.chat.id,
$response.contact.get,
)

else:
# send help
discard await b.sendMessage(
Expand Down
10 changes: 5 additions & 5 deletions src/telebot/private/helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@ import types
# -------------
func InputTextMessageContent*(messageText: string): InputMessageContent =
new(result)
result.`type` = TextMessage
result.kind = TextMessage
result.messageText = messageText

func InputLocationMessageContent*(latitude, longitude: float): InputMessageContent =
new(result)
result.`type` = LocationMessage
result.kind = LocationMessage
result.latitude = latitude
result.longitude = longitude

func InputVenueMessageContent*(latitude, longitude: float, title, address: string): InputMessageContent =
new(result)
result.`type` = VenueMessage
result.kind = VenueMessage
result.latitude = latitude
result.longitude = longitude
result.venueTitle = title
result.venueAddress = address

func InputContactMessageContent*(phoneNumber, firstName: string): InputMessageContent =
new(result)
result.`type` = ContactMessage
result.kind = ContactMessage
result.phoneNumber = phoneNumber
result.firstName = firstName

func InputInvoiceMessageContent*(title, description, payload, providerToken, currrency: string, prices: seq[LabeledPrice]): InputMessageContent =
new(result)
result.`type` = InvoiceMessage
result.kind = InvoiceMessage
result.title = title
result.description = description
result.payload = payload
Expand Down
10 changes: 5 additions & 5 deletions src/telebot/private/keyboard.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ proc initKeyBoardButton*(text: string): KeyboardButton =

proc newReplyKeyboardMarkup*(keyboards: varargs[seq[KeyboardButton]], inputFieldPlaceholder = ""): ReplyKeyboardMarkup =
new(result)
result.type = kReplyKeyboardMarkup
result.kind = kReplyKeyboardMarkup
for keyboard in keyboards:
result.keyboard.add(keyboard)
if inputFieldPlaceholder.len != 0:
Expand All @@ -16,25 +16,25 @@ proc initInlineKeyBoardButton*(text: string): InlineKeyboardButton =

proc newInlineKeyboardMarkup*(keyboards: varargs[seq[InlineKeyBoardButton]]): InlineKeyboardMarkup =
new(result)
result.type = kInlineKeyboardMarkup
result.kind = kInlineKeyboardMarkup
for keyboard in keyboards:
result.inlineKeyboard.add(keyboard)


proc newReplyKeyboardRemove*(selective: bool): ReplyKeyboardRemove =
new(result)
result.type = kReplyKeyboardRemove
result.kind = kReplyKeyboardRemove
result.selective = some(selective)

proc newForceReply*(selective: bool, inputFieldPlaceholder = ""): ForceReply =
new(result)
result.type = kForceReply
result.kind = kForceReply
result.selective = some(selective)
if inputFieldPlaceholder.len != 0:
result.inputFieldPlaceholder = some(inputFieldPlaceholder)

proc `$`*(k: KeyboardMarkup): string =
case k.type:
case k.kind:
of kInlineKeyboardMarkup:
marshal(InlineKeyboardMarkup(k), result)
of kReplyKeyboardMarkup:
Expand Down
6 changes: 4 additions & 2 deletions src/telebot/private/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ from asyncdispatch import Future

converter optionToBool*[T](o: Option[T]): bool = o.isSome()

template telebotInternalUse* {.pragma.}

type
TelegramObject* = object of RootObj

Expand Down Expand Up @@ -259,7 +261,7 @@ type
kInlineKeyboardMarkup

KeyboardMarkup* = ref object of TelegramObject
`type`*: KeyboardKind
kind* {.telebotInternalUse.}: KeyboardKind
selective*: Option[bool]

ReplyKeyboardMarkup* = ref object of KeyboardMarkup
Expand Down Expand Up @@ -597,7 +599,7 @@ type
InvoiceMessage

InputMessageContent* = ref object of TelegramObject
case `type`*: InputMessageContentKind
case kind* {.telebotInternalUse.}: InputMessageContentKind
of TextMessage:
messageText*: string
parseMode*: Option[string]
Expand Down
70 changes: 24 additions & 46 deletions src/telebot/private/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,31 @@ proc formatName*(s: string): string =
if s == "fromUser":
return "from"

result = newStringOfCap(s.len + 5)
for c in s:
if c in {'A'..'Z'}:
result.add("_")
result.add(c.toLowerAscii)
else:
result.add(c)
# optimize: dont alloc new string if not needed
var hasUpperChar = false
for i in 0..<s.len:
if s[i] in {'A'..'Z'}:
hasUpperChar = true
break

if hasUpperChar:
result = newStringOfCap(s.len + 5)
for c in s:
if c in {'A'..'Z'}:
result.add("_")
result.add(c.toLowerAscii)
else:
result.add(c)
else:
return s

proc put*[T](s: var seq[T], n: JsonNode) {.inline.}

proc unmarshal*(n: JsonNode, T: typedesc): T =
when result is object or result is tuple:
when T is TelegramObject:
for name, value in result.fieldPairs:
let jsonKey = formatName(name)
# DIRTY hack to make internal fields invisible
if jsonKey != "type":
when not value.hasCustomPragma(telebotInternalUse):
let jsonKey = formatName(name)
when value.type is Option:
if n.hasKey(jsonKey):
toOption(value, n[jsonKey])
Expand All @@ -101,7 +110,7 @@ proc unmarshal*(n: JsonNode, T: typedesc): T =
elif result is ref:
if n.kind != JNull:
new(result)
unmarshal(n, result[])
result[] = unmarshal(n, result[].type)
elif result is array or result is seq:
when result is seq:
newSeq(result, n.len)
Expand Down Expand Up @@ -131,9 +140,8 @@ proc marshal*[T](t: T, s: var string) =
elif t is object:
s.add "{"
for name, value in t.fieldPairs:
const jsonKey = formatName(name)
# DIRTY hack to make internal fields invisible
if name != "type":
when not value.hasCustomPragma(telebotInternalUse):
let jsonKey = formatName(name)
when value is Option:
if value.isSome:
s.add("\"" & jsonKey & "\":")
Expand Down Expand Up @@ -169,38 +177,8 @@ proc marshal*[T](t: T, s: var string) =
proc put*[T](s: var seq[T], n: JsonNode) {.inline.} =
s.add(unmarshal(n, T))

proc unref*[T: TelegramObject](r: ref T, n: JsonNode ): ref T {.inline.} =
new(result)
result[] = unmarshal(n, T)

# DIRTY hack to unmarshal keyboard markups
when result is InlineKeyboardMarkup:
result.type = kInlineKeyboardMarkup
elif result is ReplyKeyboardMarkup:
result.type = kReplyKeyboardMarkup
elif result is ReplyKeyboardRemove:
result.type = kReplyKeyboardRemove
elif result is ForceReply:
result.type = kForceReply


proc toOption*[T](o: var Option[T], n: JsonNode) {.inline.} =
when T is TelegramObject:
o = some(unmarshal(n, T))
elif T is int:
o = some(n.getInt)
elif T is string:
o = some(n.getStr)
elif T is bool:
o = some(n.getBool)
elif T is seq:
var arr: T = @[]
for item in n:
arr.put(item)
o = some(arr)
elif T is ref:
var res: T
o = some(unref(res, n))
o = some(unmarshal(n, T))

proc makeRequest*(b: Telebot, `method`: string, data: MultipartData = nil): Future[JsonNode] {.async.} =
let endpoint = API_URL % [b.serverUrl, b.token, `method`]
Expand Down
2 changes: 1 addition & 1 deletion telebot.nimble
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "2022.01.02"
version = "2022.01.07"
author = "Huy Doan"
description = "Async Telegram Bot API Client"
license = "MIT"
Expand Down

0 comments on commit 858a750

Please sign in to comment.