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