1 module telega.telegram.basic; 2 3 import std.typecons : Nullable, nullable; 4 import asdf : Asdf, serializedAs, deserialize; 5 import telega.serialization : JsonableAlgebraicProxy, SerializableEnumProxy, serializeToJsonString; 6 import telega.botapi : BotApi, TelegramMethod, HTTPMethod, ChatId, isTelegramId; 7 import telega.telegram.stickers : Sticker; 8 import telega.telegram.games : Game, Animation, CallbackGame; 9 import telega.telegram.payments : Invoice, SuccessfulPayment, ShippingQuery, PreCheckoutQuery; 10 import telega.telegram.inline : InlineQuery; 11 import telega.telegram.poll : Poll, PollAnswer; 12 13 14 version (unittest) 15 { 16 import telega.test : assertEquals; 17 } 18 19 /******************************************************************/ 20 /* Telegram types and enums */ 21 /******************************************************************/ 22 23 struct User 24 { 25 int id; 26 bool is_bot; 27 string first_name; 28 29 Nullable!string last_name; 30 Nullable!string username; 31 Nullable!string language_code; 32 } 33 34 unittest 35 { 36 string json = `{ 37 "id": 42, 38 "is_bot": false, 39 "first_name": "FirstName" 40 }`; 41 42 User u = deserialize!User(json); 43 44 u.last_name.isNull 45 .assertEquals(true); 46 } 47 48 unittest 49 { 50 string json = `{ 51 "id": 42, 52 "is_bot": false, 53 "first_name": "FirstName", 54 "last_name": "LastName" 55 }`; 56 57 User u = deserialize!User(json); 58 59 u.last_name.isNull 60 .assertEquals(false); 61 } 62 63 64 65 @serializedAs!ChatTypeProxy 66 enum ChatType : string 67 { 68 Private = "private", 69 Group = "group", 70 Supergroup = "supergroup", 71 Channel = "channel" 72 } 73 74 struct ChatTypeProxy 75 { 76 ChatType t; 77 78 this(ChatType type) 79 { 80 t = type; 81 } 82 83 ChatType opCast(T : ChatType)() 84 { 85 return t; 86 } 87 88 static ChatTypeProxy deserialize(Asdf v) 89 { 90 return ChatTypeProxy(cast(ChatType)cast(string)v); 91 } 92 } 93 94 struct Chat 95 { 96 long id; 97 ChatType type; 98 Nullable!string title; 99 Nullable!string first_name; 100 Nullable!string last_name; 101 Nullable!string username; 102 Nullable!bool all_members_are_administrators; 103 Nullable!ChatPhoto photo; 104 Nullable!string description; 105 Nullable!string invite_link; 106 // TODO Nullable!Message pinned_message; 107 Nullable!string sticker_set_name; 108 Nullable!bool can_set_sticker_set; 109 } 110 111 unittest 112 { 113 string json = `{ 114 "id": 42, 115 "type": "group", 116 "title": "chat title", 117 "all_members_are_administrators": false 118 }`; 119 120 Chat c = deserialize!Chat(json); 121 122 c.id 123 .assertEquals(42); 124 c.type 125 .assertEquals(ChatType.Group); 126 } 127 128 struct Message 129 { 130 uint message_id; 131 uint date; 132 Chat chat; 133 Nullable!User from; 134 Nullable!User forward_from; 135 Nullable!Chat forward_from_chat; 136 Nullable!uint forward_from_message_id; 137 Nullable!string forward_signature; 138 Nullable!string forward_sender_name; 139 Nullable!uint forward_date; 140 Nullable!uint edit_date; 141 Nullable!string media_group_id; 142 Nullable!string author_signature; 143 Nullable!string text; 144 Nullable!(MessageEntity[]) entities; 145 Nullable!(MessageEntity[]) caption_entities; 146 Nullable!Audio audio; 147 Nullable!Document document; 148 Nullable!Animation animation; 149 Nullable!Game game; 150 Nullable!Poll poll; 151 Nullable!(PhotoSize[]) photo; 152 Nullable!Sticker sticker; 153 Nullable!Video video; 154 Nullable!Voice voice; 155 Nullable!VideoNote video_note; 156 // TODO Nullable!Message reply_to_message; 157 // TODO Nullable!Message pinned_message; 158 Nullable!string caption; 159 Nullable!Contact contact; 160 Nullable!Location location; 161 Nullable!Venue venue; 162 Nullable!(User[]) new_chat_members; 163 Nullable!User left_chat_member; 164 Nullable!string new_chat_title; 165 Nullable!(PhotoSize[]) new_chat_photo; 166 Nullable!bool delete_chat_photo; 167 Nullable!bool group_chat_created; 168 Nullable!bool supergroup_chat_created; 169 Nullable!bool channel_chat_created; 170 Nullable!long migrate_to_chat_id; 171 Nullable!long migrate_from_chat_id; 172 Nullable!Invoice invoice; 173 Nullable!SuccessfulPayment successful_payment; 174 Nullable!string connected_website; 175 176 @property 177 uint id() 178 { 179 return message_id; 180 } 181 } 182 183 unittest 184 { 185 string json = `{ 186 "message_id": 1, 187 "date": 2, 188 "chat": { 189 "id": 11, 190 "type": "private" 191 }, 192 "photo": [], 193 "new_chat_photo": [ 194 { 195 "file_id": "fid", 196 "width" : 3, 197 "height": 4, 198 "file_size": 450 199 } 200 ] 201 }`; 202 203 auto m = deserialize!Message(json); 204 assertEquals(m.message_id, 1); 205 assertEquals(m.chat.id, 11); 206 207 assertEquals(true, m.from.isNull); 208 assertEquals(false, m.photo.isNull); 209 assertEquals(true, m.photo.get is null); 210 211 assertEquals(false, m.new_chat_photo.isNull); 212 assertEquals(false, m.new_chat_photo.get is null); 213 assertEquals(1, m.new_chat_photo.get.length); 214 assertEquals("fid", m.new_chat_photo.get[0].file_id); 215 assertEquals(false, m.new_chat_photo.get[0].file_size.isNull); 216 assertEquals(450, m.new_chat_photo.get[0].file_size.get); 217 } 218 219 struct Update 220 { 221 uint update_id; 222 Nullable!Message message; 223 224 Nullable!Message edited_message; 225 Nullable!Message channel_post; 226 Nullable!Message edited_channel_post; 227 Nullable!InlineQuery inline_query; 228 Nullable!ChosenInlineResult chosen_inline_result; 229 Nullable!CallbackQuery callback_query; 230 Nullable!ShippingQuery shipping_query; 231 Nullable!PreCheckoutQuery pre_checkout_query; 232 Nullable!Poll poll; 233 Nullable!PollAnswer poll_answer; 234 235 @property @safe @nogc nothrow pure 236 uint id() 237 { 238 return update_id; 239 } 240 } 241 242 unittest 243 { 244 string json = `{ 245 "update_id": 143, 246 "message": { 247 "message_id": 243, 248 "text": "message text" 249 } 250 }`; 251 252 Update u = deserialize!Update(json); 253 254 u.id 255 .assertEquals(143); 256 u.message.message_id 257 .assertEquals(243); 258 u.message.text.get 259 .assertEquals("message text"); 260 } 261 262 enum ParseMode : string 263 { 264 Markdown = "Markdown", 265 HTML = "HTML", 266 None = "", 267 } 268 269 enum MessageEntityType : string 270 { 271 Mention = "mention", 272 Hashtag = "hashtag", 273 Cashtag = "cashtag", 274 BotCommand = "bot_command", 275 Url = "url", 276 Email = "email", 277 PhoneNumber = "phone_number", 278 Bold = "bold", 279 Italic = "italic", 280 Underline = "underline", 281 Strikethrough = "strikethrough", 282 Code = "code", 283 Pre = "pre", 284 TextLink = "text_link", 285 TextMension = "text_mention" 286 } 287 288 struct MessageEntity 289 { 290 MessageEntityType type; 291 uint offset; 292 uint length; 293 Nullable!string url; 294 Nullable!User user; 295 } 296 297 struct PhotoSize 298 { 299 string file_id; 300 int width; 301 int height; 302 303 Nullable!uint file_size; 304 } 305 306 struct Audio 307 { 308 string file_id; 309 uint duration; 310 Nullable!string performer; 311 Nullable!string title; 312 Nullable!string mime_type; 313 Nullable!uint file_size; 314 Nullable!PhotoSize thumb; 315 } 316 317 // TODO Add Nullable fields 318 struct Document 319 { 320 string file_id; 321 PhotoSize thumb; 322 string file_name; 323 string mime_type; 324 uint file_size; 325 } 326 327 // TODO Add Nullable fields 328 struct Video 329 { 330 string file_id; 331 uint width; 332 uint height; 333 uint duration; 334 PhotoSize thumb; 335 string mime_type; 336 uint file_size; 337 } 338 339 // TODO Add Nullable fields 340 struct Voice 341 { 342 string file_id; 343 uint duration; 344 string mime_type; 345 uint file_size; 346 } 347 348 // TODO Add Nullable fields 349 struct VideoNote 350 { 351 string file_id; 352 uint length; 353 uint duration; 354 PhotoSize thumb; 355 uint file_size; 356 } 357 358 struct Contact 359 { 360 string phone_number; 361 string first_name; 362 Nullable!string last_name; 363 Nullable!uint user_id; 364 Nullable!string vcard; 365 } 366 367 unittest 368 { 369 string json = `{ 370 "phone_number": "+123456789", 371 "first_name": "FirstName", 372 "last_name": "LstName", 373 "user_id": 42 374 }`; 375 376 Contact c = deserialize!Contact(json); 377 378 c.phone_number.assertEquals("+123456789"); 379 c.user_id.assertEquals(42); 380 } 381 382 // TODO Add Nullable fields 383 struct Location 384 { 385 float longitude; 386 float latitude; 387 } 388 389 struct Venue 390 { 391 Location location; 392 string title; 393 string address; 394 Nullable!string foursquare_id; 395 Nullable!string foursquare_type; 396 } 397 398 // TODO Add Nullable fields 399 struct UserProfilePhotos 400 { 401 uint total_count; 402 PhotoSize[][] photos; 403 } 404 405 // TODO Add Nullable fields 406 struct File 407 { 408 string file_id; 409 uint file_size; 410 string file_path; 411 } 412 413 414 import std.meta : AliasSeq, staticIndexOf; 415 import std.variant : Algebraic; 416 417 alias ReplyMarkupStructs = AliasSeq!( 418 ReplyKeyboardMarkup, 419 ReplyKeyboardRemove, 420 InlineKeyboardMarkup, 421 ForceReply 422 ); 423 424 /** 425 Abstract structure for unioining ReplyKeyboardMarkup, ReplyKeyboardRemove, 426 InlineKeyboardMarkup and ForceReply 427 */ 428 429 alias ReplyMarkup = JsonableAlgebraicProxy!ReplyMarkupStructs; 430 enum isReplyMarkup(T) = 431 is(T == ReplyMarkup) || staticIndexOf!(T, ReplyMarkupStructs) >= 0; 432 433 import std.algorithm.iteration; 434 import std.array; 435 436 struct ReplyKeyboardMarkup 437 { 438 KeyboardButton[][] keyboard; 439 440 Nullable!bool resize_keyboard; 441 Nullable!bool one_time_keyboard; 442 Nullable!bool selective; 443 444 this (string[][] keyboard) 445 { 446 this.keyboard = keyboard.map!toKeyboardButtonRow.array; 447 } 448 449 void opOpAssign(string op : "~")(KeyboardButton[] buttons) 450 { 451 keyboard ~= buttons; 452 } 453 } 454 455 struct KeyboardButton 456 { 457 string text; 458 459 Nullable!bool request_contact; 460 Nullable!bool request_location; 461 462 this(string text) 463 { 464 this.text = text; 465 } 466 467 this(string text, bool requestContact) 468 { 469 this(text); 470 this.request_contact = requestContact; 471 } 472 473 this(string text, bool requestContact, bool requestLocation) 474 { 475 this(text, requestContact); 476 this.request_location = requestLocation; 477 } 478 479 typeof(this) requestContact(bool value = true) 480 { 481 request_contact = value; 482 483 return this; 484 } 485 486 typeof(this) requestLocation(bool value = true) 487 { 488 request_location = value; 489 490 return this; 491 } 492 } 493 494 KeyboardButton[] toKeyboardButtonRow(string[] row) 495 { 496 return row.map!(b => KeyboardButton(b)).array; 497 } 498 499 struct ReplyKeyboardRemove 500 { 501 bool remove_keyboard = true; 502 Nullable!bool selective; 503 } 504 505 struct InlineKeyboardMarkup 506 { 507 InlineKeyboardButton[][] inline_keyboard; 508 } 509 510 struct InlineKeyboardButton 511 { 512 string text; 513 Nullable!string url; 514 Nullable!string callback_data; 515 Nullable!string switch_inline_query; 516 Nullable!string switch_inline_query_current_chat; 517 Nullable!CallbackGame callback_game; 518 Nullable!bool pay; 519 } 520 521 // TODO Add Nullable fields 522 struct CallbackQuery 523 { 524 string id; 525 User from; 526 Nullable!Message message; 527 string inline_message_id; 528 string chat_instance; 529 string data; 530 string game_short_name; 531 } 532 533 struct ForceReply 534 { 535 bool force_reply = true; 536 Nullable!bool selective; 537 } 538 539 struct ChatPhoto 540 { 541 string small_file_id; 542 string big_file_id; 543 } 544 545 // TODO Add Nullable fields 546 struct ResponseParameters 547 { 548 long migrate_to_chat_id; 549 uint retry_after; 550 } 551 552 alias InputMediaStructs = AliasSeq!( 553 InputMediaPhoto, 554 InputMediaVideo, 555 InputMediaAnimation, 556 InputMediaAudio, 557 InputMediaDocument 558 ); 559 560 alias InputMedia = JsonableAlgebraicProxy!InputMediaStructs; 561 562 struct InputMediaPhoto 563 { 564 string type; 565 string media; 566 Nullable!string caption; 567 Nullable!ParseMode parse_mode; 568 } 569 570 struct InputMediaVideo 571 { 572 string type; 573 string media; 574 Nullable!string caption; 575 Nullable!ParseMode parse_mode; 576 Nullable!uint width; 577 Nullable!uint height; 578 Nullable!uint duration; 579 Nullable!bool supports_streaming; 580 } 581 582 struct InputMediaAnimation 583 { 584 string type = "animation"; 585 string media; 586 Nullable!string thumb; // TODO InputFile 587 Nullable!string caption; 588 Nullable!ParseMode parse_mode; 589 Nullable!uint width; 590 Nullable!uint height; 591 Nullable!uint duration; 592 } 593 594 struct InputMediaAudio 595 { 596 string type = "audio"; 597 string media; 598 Nullable!string thumb; // TODO InputFile 599 Nullable!string caption; 600 Nullable!ParseMode parse_mode; 601 Nullable!uint duration; 602 Nullable!string performer; 603 Nullable!string title; 604 } 605 606 struct InputMediaDocument 607 { 608 string type = "document"; 609 string media; 610 Nullable!string thumb; // TODO InputFile 611 Nullable!string caption; 612 Nullable!ParseMode parse_mode; 613 } 614 615 struct InputFile 616 { 617 // no fields 618 } 619 620 alias InputMessageContentStructs = AliasSeq!( 621 InputTextMessageContent, InputLocationMessageContent, InputVenueMessageContent, InputContactMessageContent 622 ); 623 624 alias InputMessageContent = JsonableAlgebraicProxy!InputMessageContentStructs; 625 626 struct InputTextMessageContent 627 { 628 string message_text; 629 Nullable!ParseMode parse_mode; 630 Nullable!bool disable_web_page_preview; 631 } 632 633 struct InputLocationMessageContent 634 { 635 float latitude; 636 float longitude; 637 Nullable!uint live_period; 638 } 639 640 /// outgoing 641 struct InputVenueMessageContent 642 { 643 float latitude; 644 float longitude; 645 string title; 646 string address; 647 Nullable!string foursquare_id; 648 Nullable!string foursquare_type; 649 } 650 651 unittest 652 { 653 InputVenueMessageContent ivmc = { 654 latitude : 0.01, 655 longitude : 0.02, 656 title : "t", 657 address : "a", 658 foursquare_id : "fid", 659 foursquare_type : "ft" 660 }; 661 662 ivmc.serializeToJsonString() 663 .assertEquals( 664 `{"latitude":0.01,"longitude":0.02,"title":"t","address":"a","foursquare_id":"fid","foursquare_type":"ft"}` 665 ); 666 } 667 668 struct InputContactMessageContent 669 { 670 string phone_number; 671 string first_name; 672 Nullable!string last_name; 673 Nullable!string vcard; 674 } 675 676 struct ChosenInlineResult 677 { 678 string result_id; 679 User from; 680 Nullable!Location location; 681 Nullable!string inline_message_id; 682 string query; 683 } 684 685 /******************************************************************/ 686 /* Telegram methods */ 687 /******************************************************************/ 688 689 @serializedAs!(SerializableEnumProxy!UpdateType) 690 enum UpdateType: string 691 { 692 Message = "message", 693 EditedMessage = "edited_message", 694 ChannelPost = "channel_post", 695 EditedChannelPost = "edited_channel_post", 696 InlineQuery = "inline_query", 697 ChosenInlineResult = "chosen_inline_result", 698 CallbackQuery = "callback_query", 699 ShippingQuery = "shipping_query", 700 PreCheckoutQuery = "pre_checkout_query", 701 Poll = "poll", 702 PollAnswer = "poll_answer" 703 } 704 705 struct GetUpdatesMethod 706 { 707 enum ubyte DEFAULT_LIMIT = 5; 708 enum uint DEFAULT_TIMEOUT = 30; 709 710 mixin TelegramMethod!"/getUpdates"; 711 712 Nullable!int offset; 713 Nullable!ubyte limit; 714 Nullable!uint timeout; 715 Nullable!(UpdateType[]) allowed_updates; 716 717 void updateOffset(uint updateId) 718 { 719 import std.algorithm.comparison : max; 720 721 if (offset.isNull) { 722 offset = updateId + 1; 723 } else { 724 offset = max(offset.get, updateId) + 1; 725 } 726 } 727 } 728 729 unittest 730 { 731 GetUpdatesMethod m = { 732 offset: 1, 733 allowed_updates: [UpdateType.EditedMessage] 734 }; 735 736 m.serializeToJsonString() 737 .assertEquals(`{"offset":1,"allowed_updates":["edited_message"]}`); 738 } 739 740 struct GetMeMethod 741 { 742 mixin TelegramMethod!("/getMe", HTTPMethod.GET); 743 } 744 745 struct SendMessageMethod 746 { 747 mixin TelegramMethod!"/sendMessage"; 748 749 ChatId chat_id; 750 string text; 751 Nullable!ParseMode parse_mode; 752 Nullable!bool disable_web_page_preview; 753 Nullable!bool disable_notification; 754 Nullable!uint reply_to_message_id; 755 756 ReplyMarkup reply_markup; 757 } 758 759 unittest 760 { 761 SendMessageMethod m = { 762 chat_id: 111, 763 text: "Message text" 764 }; 765 766 m.serializeToJsonString() 767 .assertEquals(`{"chat_id":"111","text":"Message text"}`); 768 } 769 770 struct ForwardMessageMethod 771 { 772 mixin TelegramMethod!"/forwardMessage"; 773 774 ChatId chat_id; 775 string from_chat_id; 776 Nullable!bool disable_notification; 777 uint message_id; 778 } 779 780 struct SendPhotoMethod 781 { 782 mixin TelegramMethod!"/sendPhoto"; 783 784 ChatId chat_id; 785 string photo; 786 Nullable!string caption; 787 Nullable!ParseMode parse_mode; 788 Nullable!bool disable_notification; 789 Nullable!uint reply_to_message_id; 790 Nullable!ReplyMarkup reply_markup; 791 } 792 793 unittest 794 { 795 SendPhotoMethod m = { 796 chat_id: "111", 797 photo: "Photo url" 798 }; 799 800 import std.stdio; 801 802 m.serializeToJsonString().writeln; 803 804 m.serializeToJsonString() 805 .assertEquals(`{"chat_id":"111","photo":"Photo url"}`); 806 } 807 unittest 808 { 809 SendPhotoMethod m = { 810 chat_id: "111", 811 photo: "Photo url", 812 disable_notification: false, 813 reply_to_message_id: 0 814 }; 815 816 m.serializeToJsonString() 817 .assertEquals(`{"chat_id":"111","photo":"Photo url","disable_notification":false,"reply_to_message_id":0}`); 818 } 819 820 struct SendAudioMethod 821 { 822 mixin TelegramMethod!"/sendAudio"; 823 824 ChatId chat_id; 825 string audio; 826 Nullable!string caption; 827 Nullable!ParseMode parse_mode; 828 Nullable!uint duration; 829 Nullable!string performer; 830 Nullable!string title; 831 Nullable!bool disable_notification; 832 Nullable!uint reply_to_message_id; 833 Nullable!ReplyMarkup reply_markup; 834 835 } 836 837 unittest 838 { 839 SendAudioMethod m = { 840 chat_id: "111", 841 audio: "data" 842 }; 843 844 m.serializeToJsonString() 845 .assertEquals(`{"chat_id":"111","audio":"data"}`); 846 } 847 848 unittest 849 { 850 SendAudioMethod m = { 851 chat_id: "111", 852 audio: "data", 853 duration: 0, 854 disable_notification:false, 855 reply_to_message_id: 0, 856 }; 857 858 m.serializeToJsonString() 859 .assertEquals( 860 `{"chat_id":"111","audio":"data","duration":0,"disable_notification":false,"reply_to_message_id":0}` 861 ); 862 } 863 864 struct SendDocumentMethod 865 { 866 mixin TelegramMethod!"/sendDocument"; 867 868 ChatId chat_id; 869 string document; 870 Nullable!string caption; 871 Nullable!ParseMode parse_mode; 872 Nullable!bool disable_notification; 873 Nullable!uint reply_to_message_id; 874 Nullable!ReplyMarkup reply_markup; 875 } 876 877 unittest 878 { 879 SendDocumentMethod m = { 880 chat_id: "111", 881 document: "data" 882 }; 883 884 m.serializeToJsonString() 885 .assertEquals(`{"chat_id":"111","document":"data"}`); 886 } 887 888 unittest 889 { 890 SendDocumentMethod m = { 891 chat_id: "111", 892 document: "data", 893 disable_notification: false, 894 reply_to_message_id: 0, 895 }; 896 897 m.serializeToJsonString() 898 .assertEquals(`{"chat_id":"111","document":"data","disable_notification":false,"reply_to_message_id":0}`); 899 } 900 901 struct SendVideoMethod 902 { 903 mixin TelegramMethod!"/sendVideo"; 904 905 string chat_id; 906 string video; 907 Nullable!uint duration; 908 Nullable!uint width; 909 Nullable!uint height; 910 Nullable!string caption; 911 Nullable!ParseMode parse_mode; 912 Nullable!bool supports_streaming; 913 Nullable!bool disable_notification; 914 Nullable!uint reply_to_message_id; 915 Nullable!ReplyMarkup reply_markup; 916 } 917 918 unittest 919 { 920 SendVideoMethod m = { 921 chat_id: "111", 922 video: "data" 923 }; 924 925 m.serializeToJsonString() 926 .assertEquals(`{"chat_id":"111","video":"data"}`); 927 } 928 929 unittest 930 { 931 SendVideoMethod m = { 932 chat_id: "111", 933 video: "data", 934 duration: 0, 935 width: 0, 936 height: 0, 937 supports_streaming: false, 938 disable_notification: false, 939 reply_to_message_id: 0 940 }; 941 942 m.serializeToJsonString() 943 .assertEquals( 944 `{"chat_id":"111","video":"data","duration":0,"width":0,"height":0,"supports_streaming":false,` ~ 945 `"disable_notification":false,"reply_to_message_id":0}` 946 ); 947 } 948 949 struct SendVoiceMethod 950 { 951 mixin TelegramMethod!"/sendVoice"; 952 953 ChatId chat_id; 954 string voice; 955 Nullable!string caption; 956 Nullable!ParseMode parse_mode; 957 Nullable!uint duration; 958 Nullable!bool disable_notification; 959 Nullable!uint reply_to_message_id; 960 Nullable!ReplyMarkup reply_markup; 961 } 962 963 unittest 964 { 965 SendVoiceMethod m = { 966 chat_id: "111", 967 voice: "data" 968 }; 969 970 m.serializeToJsonString() 971 .assertEquals(`{"chat_id":"111","voice":"data"}`); 972 } 973 974 unittest 975 { 976 SendVoiceMethod m = { 977 chat_id: "111", 978 voice: "data", 979 duration: 0, 980 disable_notification: false, 981 reply_to_message_id: 0, 982 }; 983 984 m.serializeToJsonString() 985 .assertEquals( 986 `{"chat_id":"111","voice":"data","duration":0,"disable_notification":false,"reply_to_message_id":0}` 987 ); 988 } 989 990 struct SendVideoNoteMethod 991 { 992 mixin TelegramMethod!"/sendVideoNote"; 993 994 ChatId chat_id; 995 string video_note; 996 Nullable!uint duration; 997 Nullable!uint length; 998 Nullable!bool disable_notification; 999 Nullable!uint reply_to_message_id; 1000 Nullable!ReplyMarkup reply_markup; 1001 } 1002 1003 unittest 1004 { 1005 SendVideoNoteMethod m = { 1006 chat_id: "111", 1007 video_note: "data" 1008 }; 1009 1010 m.serializeToJsonString() 1011 .assertEquals(`{"chat_id":"111","video_note":"data"}`); 1012 } 1013 1014 struct SendMediaGroupMethod 1015 { 1016 mixin TelegramMethod!"/sendMediaGroup"; 1017 1018 ChatId chat_id; 1019 InputMedia[] media; 1020 Nullable!bool disable_notification; 1021 Nullable!uint reply_to_message_id; 1022 } 1023 1024 unittest 1025 { 1026 InputMedia im; 1027 1028 InputMediaPhoto imp = { 1029 type: "t", 1030 media: "m" 1031 }; 1032 1033 im = imp; 1034 1035 SendMediaGroupMethod m = { 1036 chat_id: "111", 1037 media: [im], 1038 }; 1039 1040 m.serializeToJsonString() 1041 .assertEquals(`{"chat_id":"111","media":[{"type":"t","media":"m"}]}`); 1042 } 1043 1044 struct SendLocationMethod 1045 { 1046 mixin TelegramMethod!"/sendLocation"; 1047 1048 ChatId chat_id; 1049 float latitude; 1050 float longitude; 1051 Nullable!uint live_period; 1052 Nullable!bool disable_notification; 1053 Nullable!uint reply_to_message_id; 1054 Nullable!ReplyMarkup reply_markup; 1055 } 1056 1057 unittest 1058 { 1059 SendLocationMethod m = { 1060 chat_id: "111", 1061 latitude: 0.01, 1062 longitude: 0.02 1063 }; 1064 1065 m.serializeToJsonString() 1066 .assertEquals(`{"chat_id":"111","latitude":0.01,"longitude":0.02}`); 1067 } 1068 1069 struct EditMessageLiveLocationMethod 1070 { 1071 mixin TelegramMethod!"/editMessageLiveLocation"; 1072 1073 ChatId chat_id; 1074 uint message_id; 1075 string inline_message_id; 1076 float latitude; 1077 float longitude; 1078 ReplyMarkup reply_markup; 1079 } 1080 1081 struct StopMessageLiveLocationMethod 1082 { 1083 mixin TelegramMethod!"/stopMessageLiveLocation"; 1084 1085 ChatId chat_id; 1086 uint message_id; 1087 string inline_message_id; 1088 ReplyMarkup reply_markup; 1089 } 1090 1091 struct SendVenueMethod 1092 { 1093 mixin TelegramMethod!"/sendVenue"; 1094 1095 ChatId chat_id; 1096 float latitude; 1097 float longitude; 1098 string title; 1099 string address; 1100 Nullable!string foursquare_id; 1101 Nullable!string foursquare_type; 1102 Nullable!bool disable_notification; 1103 Nullable!uint reply_to_message_id; 1104 Nullable!ReplyMarkup reply_markup; 1105 } 1106 1107 unittest 1108 { 1109 SendVenueMethod m = { 1110 chat_id: "111", 1111 latitude: 0.01, 1112 longitude: 0.02, 1113 title: "t", 1114 address: "a" 1115 }; 1116 1117 m.serializeToJsonString() 1118 .assertEquals(`{"chat_id":"111","latitude":0.01,"longitude":0.02,"title":"t","address":"a"}`); 1119 } 1120 1121 struct SendContactMethod 1122 { 1123 mixin TelegramMethod!"/sendContact"; 1124 1125 ChatId chat_id; 1126 string phone_number; 1127 string first_name; 1128 Nullable!string last_name; 1129 Nullable!string vcard; 1130 Nullable!bool disable_notification; 1131 Nullable!uint reply_to_message_id; 1132 Nullable!ReplyMarkup reply_markup; 1133 } 1134 1135 unittest 1136 { 1137 SendContactMethod m = { 1138 chat_id: "111", 1139 phone_number: "+7123", 1140 first_name: "fn" 1141 }; 1142 1143 m.serializeToJsonString() 1144 .assertEquals(`{"chat_id":"111","phone_number":"+7123","first_name":"fn"}`); 1145 } 1146 1147 enum ChatAction : string 1148 { 1149 Typing = "typing", 1150 UploadPhoto = "upload_photo", 1151 RecordVideo = "record_video", 1152 UploadVideo = "upload_video", 1153 RecordAudio = "record_audio", 1154 UploadAudio = "upload_audio", 1155 UploadDocument = "upload_document", 1156 FindLocation = "find_location", 1157 RecordVideoNote = "record_video_note", 1158 UploadVideoNote = "upload_video_note" 1159 } 1160 1161 struct SendChatActionMethod 1162 { 1163 mixin TelegramMethod!"/sendChatAction"; 1164 1165 ChatId chat_id; 1166 string action; 1167 } 1168 1169 struct GetUserProfilePhotosMethod 1170 { 1171 mixin TelegramMethod!("/getUserProfilePhotos", HTTPMethod.GET); 1172 1173 int user_id; 1174 uint offset; 1175 uint limit; 1176 } 1177 1178 struct GetFileMethod 1179 { 1180 mixin TelegramMethod!("/getFile", HTTPMethod.GET); 1181 1182 string file_id; 1183 } 1184 1185 struct GetChatMethod 1186 { 1187 mixin TelegramMethod!("/getChat", HTTPMethod.GET); 1188 1189 ChatId chat_id; 1190 } 1191 1192 struct AnswerCallbackQueryMethod 1193 { 1194 mixin TelegramMethod!"/answerCallbackQuery"; 1195 1196 string callback_query_id; 1197 string text; 1198 bool show_alert; 1199 string url; 1200 uint cache_time; 1201 } 1202 1203 1204 // API methods 1205 1206 Update[] getUpdates(BotApi api, ref GetUpdatesMethod m) 1207 { 1208 return api.callMethod!(Update[])(m); 1209 } 1210 1211 Update[] getUpdates(BotApi api, int offset, ubyte limit = 5, uint timeout = 30, UpdateType[] allowedUpdates = []) 1212 { 1213 GetUpdatesMethod m = { 1214 offset: offset, 1215 limit: limit, 1216 timeout: timeout, 1217 allowed_updates: allowedUpdates.nullable 1218 }; 1219 1220 return api.getUpdates(m); 1221 } 1222 1223 User getMe(BotApi api) 1224 { 1225 GetMeMethod m; 1226 1227 return api.callMethod!(User, GetMeMethod)(m); 1228 } 1229 1230 Message sendMessage(BotApi api, ref SendMessageMethod m) 1231 { 1232 return api.callMethod!(Message, SendMessageMethod)(m); 1233 } 1234 1235 Message sendMessage(T)(BotApi api, T chatId, string text) 1236 if (isTelegramId!T) 1237 { 1238 SendMessageMethod m = { 1239 chat_id : chatId, 1240 text : text, 1241 }; 1242 1243 return sendMessage(api, m); 1244 } 1245 1246 Message forwardMessage(T1, T2)(BotApi api, T1 chatId, T2 fromChatId, uint messageId) 1247 if (isTelegramId!T1 && isTelegramId!T2) 1248 { 1249 ForwardMessageMethod m = { 1250 message_id : messageId, 1251 chat_id : chatId, 1252 from_chat_id: fromChatId, 1253 }; 1254 1255 return api.callMethod!(Message, ForwardMessageMethod)(m); 1256 } 1257 1258 Message forwardMessage(BotApi api, ref ForwardMessageMethod m) 1259 { 1260 return api.callMethod!(Message, ForwardMessageMethod)(m); 1261 } 1262 1263 Message sendPhoto(BotApi api, ref SendPhotoMethod m) 1264 { 1265 return api.callMethod!(Message, SendPhotoMethod)(m); 1266 } 1267 1268 Message sendPhoto(T1)(BotApi api, T1 chatId, string photo) 1269 if (isTelegramId!T1) 1270 { 1271 SendPhotoMethod m = { 1272 chat_id : chatId, 1273 photo : photo, 1274 }; 1275 1276 return sendPhoto(api, m); 1277 } 1278 1279 Message sendAudio(BotApi api, ref SendAudioMethod m) 1280 { 1281 return api.callMethod!(Message, SendAudioMethod)(m); 1282 } 1283 1284 Message sendAudio(T1)(BotApi api, T1 chatId, string audio) 1285 if (isTelegramId!T1) 1286 { 1287 SendAudioMethod m = { 1288 chat_id : chatId, 1289 audio : audio 1290 }; 1291 1292 return sendAudio(api, m); 1293 } 1294 1295 Message sendDocument(BotApi api, ref SendDocumentMethod m) 1296 { 1297 return api.callMethod!(Message, SendDocumentMethod)(m); 1298 } 1299 1300 Message sendDocument(T1)(BotApi api, T1 chatId, string document) 1301 if (isTelegramId!T1) 1302 { 1303 SendDocumentMethod m = { 1304 chat_id : chatId, 1305 document : document 1306 }; 1307 1308 return sendDocument(api, m); 1309 } 1310 1311 Message sendVideo(BotApi api, ref SendVideoMethod m) 1312 { 1313 return api.callMethod!(Message, SendVideoMethod)(m); 1314 } 1315 1316 Message sendVideo(T1)(BotApi api, T1 chatId, string video) 1317 if (isTelegramId!T1) 1318 { 1319 SendVideoMethod m = { 1320 chat_id : chatId, 1321 video : video 1322 }; 1323 1324 return sendVideo(api, m); 1325 } 1326 1327 Message sendVoice(BotApi api, ref SendVoiceMethod m) 1328 { 1329 return api.callMethod!(Message, SendVoiceMethod)(m); 1330 } 1331 1332 Message sendVoice(T1)(BotApi api, T1 chatId, string voice) 1333 if (isTelegramId!T1) 1334 { 1335 SendVoiceMethod m = { 1336 chat_id : chatId, 1337 voice : voice 1338 }; 1339 1340 return sendVoice(api, m); 1341 } 1342 1343 Message sendVideoNote(BotApi api, ref SendVideoNoteMethod m) 1344 { 1345 return api.callMethod!(Message, SendVideoNoteMethod)(m); 1346 } 1347 1348 Message sendVideoNote(T1)(BotApi api, T1 chatId, string videoNote) 1349 if (isTelegramId!T1) 1350 { 1351 SendVideoNoteMethod m = { 1352 chat_id : chatId, 1353 video_note : videoNote 1354 }; 1355 1356 return sendVideoNote(api, m); 1357 } 1358 1359 Message sendMediaGroup(BotApi api, ref SendMediaGroupMethod m) 1360 { 1361 return api.callMethod!(Message, SendMediaGroupMethod)(m); 1362 } 1363 1364 Message sendMediaGroup(T1)(BotApi api, T1 chatId, InputMedia[] media) 1365 if (isTelegramId!T1) 1366 { 1367 SendMediaGroupMethod m = { 1368 chat_id : chatId, 1369 media : media 1370 }; 1371 1372 return sendMediaGroup(api, m); 1373 } 1374 1375 Message sendLocation(BotApi api, ref SendLocationMethod m) 1376 { 1377 return api.callMethod!(Message, SendLocationMethod)(m); 1378 } 1379 1380 Message sendLocation(T1)(BotApi api, T1 chatId, float latitude, float longitude) 1381 if (isTelegramId!T1) 1382 { 1383 SendLocationMethod m = { 1384 chat_id : chatId, 1385 latitude : latitude, 1386 longitude : longitude, 1387 }; 1388 1389 return sendLocation(api, m); 1390 } 1391 1392 Nullable!Message editMessageLiveLocation(BotApi api, ref EditMessageLiveLocationMethod m) 1393 { 1394 return api.callMethod!(Nullable!Message, EditMessageLiveLocationMethod)(m); 1395 } 1396 1397 Nullable!Message editMessageLiveLocation(BotApi api, string inlineMessageId, float latitude, float longitude) 1398 { 1399 EditMessageLiveLocationMethod m = { 1400 inline_message_id : inlineMessageId, 1401 latitude : latitude, 1402 longitude : longitude 1403 }; 1404 1405 return editMessageLiveLocation(api, m); 1406 } 1407 1408 Nullable!Message editMessageLiveLocation(T1)(BotApi api, T1 chatId, uint messageId, float latitude, float longitude) 1409 if (isTelegramId!T1) 1410 { 1411 EditMessageLiveLocationMethod m = { 1412 chat_id : chatId, 1413 message_id : messageId, 1414 latitude : latitude, 1415 longitude : longitude 1416 }; 1417 1418 return editMessageLiveLocation(api, m); 1419 } 1420 1421 Nullable!Message stopMessageLiveLocation(BotApi api, ref StopMessageLiveLocationMethod m) 1422 { 1423 return api.callMethod!(Nullable!Message, StopMessageLiveLocationMethod)(m); 1424 } 1425 1426 Nullable!Message stopMessageLiveLocation(BotApi api, string inlineMessageId) 1427 { 1428 StopMessageLiveLocationMethod m = { 1429 inline_message_id : inlineMessageId 1430 }; 1431 1432 return stopMessageLiveLocation(api, m); 1433 } 1434 1435 Nullable!Message stopMessageLiveLocation(T1)(BotApi api, T1 chatId, uint messageId) 1436 if (isTelegramId!T1) 1437 { 1438 StopMessageLiveLocationMethod m = { 1439 chat_id : chatId, 1440 message_id : messageId 1441 }; 1442 1443 return stopMessageLiveLocation(api, m); 1444 } 1445 1446 Message sendVenue(BotApi api, ref SendVenueMethod m) 1447 { 1448 return api.callMethod!(Message, SendVenueMethod)(m); 1449 } 1450 1451 Message sendVenue(T1)(BotApi api, T1 chatId, float latitude, float longitude, 1452 string title, string address) 1453 if (isTelegramId!T1) 1454 { 1455 SendVenueMethod m = { 1456 chat_id : chatId, 1457 latitude : latitude, 1458 longitude : longitude, 1459 title : title, 1460 address : address 1461 }; 1462 1463 return sendVenue(api, m); 1464 } 1465 1466 Message sendContact(BotApi api, ref SendContactMethod m) 1467 { 1468 return api.callMethod!(Message, SendContactMethod)(m); 1469 } 1470 1471 Message sendContact(T1)(BotApi api, T1 chatId, string phone_number, string first_name) 1472 if (isTelegramId!T1) 1473 { 1474 SendContactMethod m = { 1475 chat_id : chatId, 1476 phone_number : phone_number, 1477 first_name : first_name 1478 }; 1479 1480 return sendContact(api, m); 1481 } 1482 1483 bool sendChatAction(BotApi api, ref SendChatActionMethod m) 1484 { 1485 return api.callMethod!(bool, SendChatActionMethod)(m); 1486 } 1487 1488 bool sendChatAction(T1)(BotApi api, T1 chatId, string action) 1489 if (isTelegramId!T1) 1490 { 1491 SendChatActionMethod m = { 1492 chat_id : chatId, 1493 action : action 1494 }; 1495 1496 return sendChatAction(api, m); 1497 } 1498 1499 UserProfilePhotos getUserProfilePhotos(BotApi api, ref GetUserProfilePhotosMethod m) 1500 { 1501 return api.callMethod!(UserProfilePhotos, GetUserProfilePhotosMethod)(m); 1502 } 1503 1504 UserProfilePhotos getUserProfilePhotos(BotApi api, int userId) 1505 { 1506 GetUserProfilePhotosMethod m = { 1507 user_id : userId 1508 }; 1509 1510 return getUserProfilePhotos(api, m); 1511 } 1512 1513 File getFile(BotApi api, ref GetFileMethod m) 1514 { 1515 return api.callMethod!(File, GetFileMethod)(m); 1516 } 1517 1518 File getFile(BotApi api, string fileId) 1519 { 1520 GetFileMethod m = { 1521 file_id : fileId 1522 }; 1523 1524 return getFile(api, m); 1525 } 1526 1527 1528 1529 Chat getChat(BotApi api, ref GetChatMethod m) 1530 { 1531 return api.callMethod!Chat(m); 1532 } 1533 1534 Chat getChat(T1)(BotApi api, T1 chatId) 1535 if (isTelegramId!T1) 1536 { 1537 GetChatMethod m = { 1538 chat_id : chatId, 1539 }; 1540 1541 return getChat(api, m); 1542 } 1543 1544 bool answerCallbackQuery(BotApi api, ref AnswerCallbackQueryMethod m) 1545 { 1546 return api.callMethod!bool(m); 1547 } 1548 1549 bool answerCallbackQuery(BotApi api, string callbackQueryId) 1550 { 1551 AnswerCallbackQueryMethod m = { 1552 callback_query_id : callbackQueryId 1553 }; 1554 1555 return answerCallbackQuery(api, m); 1556 } 1557 1558 unittest 1559 { 1560 class BotApiMock : BotApi 1561 { 1562 this(string token) 1563 { 1564 super(token); 1565 } 1566 1567 T callMethod(T, M)(M method) 1568 { 1569 T result; 1570 1571 logDiagnostic("[%d] Requesting %s", requestCounter, method.name); 1572 1573 return result; 1574 } 1575 } 1576 1577 auto api = new BotApiMock(null); 1578 1579 api.getUpdates(5,30); 1580 api.getMe(); 1581 api.sendMessage("chat-id", "hello"); 1582 api.forwardMessage("chat-id", "from-chat-id", 123); 1583 api.sendPhoto("chat-id", "photo-url"); 1584 api.sendAudio("chat-id", "audio-url"); 1585 api.sendDocument("chat-id", "document-url"); 1586 api.sendVideo("chat-id", "video-url"); 1587 api.sendVoice("chat-id", "voice-url"); 1588 api.sendVideoNote("chat-id", "video-note-url"); 1589 api.sendMediaGroup("chat-id", []); 1590 api.sendLocation("chat-id", 123, 123); 1591 api.editMessageLiveLocation("chat-id", 1, 1.23, 4.56); 1592 api.editMessageLiveLocation("inline-message-id", 1.23, 4.56); 1593 api.stopMessageLiveLocation("chat-id", 1); 1594 api.stopMessageLiveLocation("inline-message-id"); 1595 api.sendVenue("chat-id", 123, 123, "title", "address"); 1596 api.sendContact("chat-id", "+123", "First Name"); 1597 api.sendChatAction("chat-id", "typing"); 1598 api.getUserProfilePhotos(1); 1599 api.getFile("file-id"); 1600 api.getChat("chat-id"); 1601 api.answerCallbackQuery("callback-query-id"); 1602 }