1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/telegram/DialogAction.h"
8 
9 #include "td/telegram/misc.h"
10 #include "td/telegram/ServerMessageId.h"
11 
12 #include "td/utils/emoji.h"
13 #include "td/utils/misc.h"
14 #include "td/utils/Slice.h"
15 #include "td/utils/SliceBuilder.h"
16 #include "td/utils/utf8.h"
17 
18 namespace td {
19 
is_valid_emoji(string & emoji)20 bool DialogAction::is_valid_emoji(string &emoji) {
21   if (!clean_input_string(emoji)) {
22     return false;
23   }
24   return is_emoji(emoji);
25 }
26 
init(Type type)27 void DialogAction::init(Type type) {
28   type_ = type;
29   progress_ = 0;
30   emoji_.clear();
31 }
32 
init(Type type,int32 progress)33 void DialogAction::init(Type type, int32 progress) {
34   type_ = type;
35   progress_ = clamp(progress, 0, 100);
36   emoji_.clear();
37 }
38 
init(Type type,string emoji)39 void DialogAction::init(Type type, string emoji) {
40   if (is_valid_emoji(emoji)) {
41     type_ = type;
42     progress_ = 0;
43     emoji_ = std::move(emoji);
44   } else {
45     init(Type::Cancel);
46   }
47 }
48 
init(Type type,int32 message_id,string emoji,const string & data)49 void DialogAction::init(Type type, int32 message_id, string emoji, const string &data) {
50   if (ServerMessageId(message_id).is_valid() && is_valid_emoji(emoji) && check_utf8(data)) {
51     type_ = type;
52     progress_ = message_id;
53     emoji_ = PSTRING() << emoji << '\xFF' << data;
54   } else {
55     init(Type::Cancel);
56   }
57 }
58 
DialogAction(Type type,int32 progress)59 DialogAction::DialogAction(Type type, int32 progress) {
60   init(type, progress);
61 }
62 
DialogAction(td_api::object_ptr<td_api::ChatAction> && action)63 DialogAction::DialogAction(td_api::object_ptr<td_api::ChatAction> &&action) {
64   if (action == nullptr) {
65     return;
66   }
67 
68   switch (action->get_id()) {
69     case td_api::chatActionCancel::ID:
70       init(Type::Cancel);
71       break;
72     case td_api::chatActionTyping::ID:
73       init(Type::Typing);
74       break;
75     case td_api::chatActionRecordingVideo::ID:
76       init(Type::RecordingVideo);
77       break;
78     case td_api::chatActionUploadingVideo::ID: {
79       auto uploading_action = move_tl_object_as<td_api::chatActionUploadingVideo>(action);
80       init(Type::UploadingVideo, uploading_action->progress_);
81       break;
82     }
83     case td_api::chatActionRecordingVoiceNote::ID:
84       init(Type::RecordingVoiceNote);
85       break;
86     case td_api::chatActionUploadingVoiceNote::ID: {
87       auto uploading_action = move_tl_object_as<td_api::chatActionUploadingVoiceNote>(action);
88       init(Type::UploadingVoiceNote, uploading_action->progress_);
89       break;
90     }
91     case td_api::chatActionUploadingPhoto::ID: {
92       auto uploading_action = move_tl_object_as<td_api::chatActionUploadingPhoto>(action);
93       init(Type::UploadingPhoto, uploading_action->progress_);
94       break;
95     }
96     case td_api::chatActionUploadingDocument::ID: {
97       auto uploading_action = move_tl_object_as<td_api::chatActionUploadingDocument>(action);
98       init(Type::UploadingDocument, uploading_action->progress_);
99       break;
100     }
101     case td_api::chatActionChoosingLocation::ID:
102       init(Type::ChoosingLocation);
103       break;
104     case td_api::chatActionChoosingContact::ID:
105       init(Type::ChoosingContact);
106       break;
107     case td_api::chatActionStartPlayingGame::ID:
108       init(Type::StartPlayingGame);
109       break;
110     case td_api::chatActionRecordingVideoNote::ID:
111       init(Type::RecordingVideoNote);
112       break;
113     case td_api::chatActionUploadingVideoNote::ID: {
114       auto uploading_action = move_tl_object_as<td_api::chatActionUploadingVideoNote>(action);
115       init(Type::UploadingVideoNote, uploading_action->progress_);
116       break;
117     }
118     case td_api::chatActionChoosingSticker::ID:
119       init(Type::ChoosingSticker);
120       break;
121     case td_api::chatActionWatchingAnimations::ID: {
122       auto watching_animations_action = move_tl_object_as<td_api::chatActionWatchingAnimations>(action);
123       init(Type::WatchingAnimations, std::move(watching_animations_action->emoji_));
124       break;
125     }
126     default:
127       UNREACHABLE();
128       break;
129   }
130 }
131 
DialogAction(telegram_api::object_ptr<telegram_api::SendMessageAction> && action)132 DialogAction::DialogAction(telegram_api::object_ptr<telegram_api::SendMessageAction> &&action) {
133   switch (action->get_id()) {
134     case telegram_api::sendMessageCancelAction::ID:
135       init(Type::Cancel);
136       break;
137     case telegram_api::sendMessageTypingAction::ID:
138       init(Type::Typing);
139       break;
140     case telegram_api::sendMessageRecordVideoAction::ID:
141       init(Type::RecordingVideo);
142       break;
143     case telegram_api::sendMessageUploadVideoAction::ID: {
144       auto upload_video_action = move_tl_object_as<telegram_api::sendMessageUploadVideoAction>(action);
145       init(Type::UploadingVideo, upload_video_action->progress_);
146       break;
147     }
148     case telegram_api::sendMessageRecordAudioAction::ID:
149       init(Type::RecordingVoiceNote);
150       break;
151     case telegram_api::sendMessageUploadAudioAction::ID: {
152       auto upload_audio_action = move_tl_object_as<telegram_api::sendMessageUploadAudioAction>(action);
153       init(Type::UploadingVoiceNote, upload_audio_action->progress_);
154       break;
155     }
156     case telegram_api::sendMessageUploadPhotoAction::ID: {
157       auto upload_photo_action = move_tl_object_as<telegram_api::sendMessageUploadPhotoAction>(action);
158       init(Type::UploadingPhoto, upload_photo_action->progress_);
159       break;
160     }
161     case telegram_api::sendMessageUploadDocumentAction::ID: {
162       auto upload_document_action = move_tl_object_as<telegram_api::sendMessageUploadDocumentAction>(action);
163       init(Type::UploadingDocument, upload_document_action->progress_);
164       break;
165     }
166     case telegram_api::sendMessageGeoLocationAction::ID:
167       init(Type::ChoosingLocation);
168       break;
169     case telegram_api::sendMessageChooseContactAction::ID:
170       init(Type::ChoosingContact);
171       break;
172     case telegram_api::sendMessageGamePlayAction::ID:
173       init(Type::StartPlayingGame);
174       break;
175     case telegram_api::sendMessageRecordRoundAction::ID:
176       init(Type::RecordingVideoNote);
177       break;
178     case telegram_api::sendMessageUploadRoundAction::ID: {
179       auto upload_round_action = move_tl_object_as<telegram_api::sendMessageUploadRoundAction>(action);
180       init(Type::UploadingVideoNote, upload_round_action->progress_);
181       break;
182     }
183     case telegram_api::speakingInGroupCallAction::ID:
184       init(Type::SpeakingInVoiceChat);
185       break;
186     case telegram_api::sendMessageHistoryImportAction::ID: {
187       auto history_import_action = move_tl_object_as<telegram_api::sendMessageHistoryImportAction>(action);
188       init(Type::ImportingMessages, history_import_action->progress_);
189       break;
190     }
191     case telegram_api::sendMessageChooseStickerAction::ID:
192       init(Type::ChoosingSticker);
193       break;
194     case telegram_api::sendMessageEmojiInteractionSeen::ID: {
195       auto emoji_interaction_seen_action = move_tl_object_as<telegram_api::sendMessageEmojiInteractionSeen>(action);
196       init(Type::WatchingAnimations, std::move(emoji_interaction_seen_action->emoticon_));
197       break;
198     }
199     case telegram_api::sendMessageEmojiInteraction::ID: {
200       auto emoji_interaction_action = move_tl_object_as<telegram_api::sendMessageEmojiInteraction>(action);
201       init(Type::ClickingAnimatedEmoji, emoji_interaction_action->msg_id_,
202            std::move(emoji_interaction_action->emoticon_), emoji_interaction_action->interaction_->data_);
203       break;
204     }
205     default:
206       UNREACHABLE();
207       break;
208   }
209 }
210 
get_input_send_message_action() const211 tl_object_ptr<telegram_api::SendMessageAction> DialogAction::get_input_send_message_action() const {
212   switch (type_) {
213     case Type::Cancel:
214       return make_tl_object<telegram_api::sendMessageCancelAction>();
215     case Type::Typing:
216       return make_tl_object<telegram_api::sendMessageTypingAction>();
217     case Type::RecordingVideo:
218       return make_tl_object<telegram_api::sendMessageRecordVideoAction>();
219     case Type::UploadingVideo:
220       return make_tl_object<telegram_api::sendMessageUploadVideoAction>(progress_);
221     case Type::RecordingVoiceNote:
222       return make_tl_object<telegram_api::sendMessageRecordAudioAction>();
223     case Type::UploadingVoiceNote:
224       return make_tl_object<telegram_api::sendMessageUploadAudioAction>(progress_);
225     case Type::UploadingPhoto:
226       return make_tl_object<telegram_api::sendMessageUploadPhotoAction>(progress_);
227     case Type::UploadingDocument:
228       return make_tl_object<telegram_api::sendMessageUploadDocumentAction>(progress_);
229     case Type::ChoosingLocation:
230       return make_tl_object<telegram_api::sendMessageGeoLocationAction>();
231     case Type::ChoosingContact:
232       return make_tl_object<telegram_api::sendMessageChooseContactAction>();
233     case Type::StartPlayingGame:
234       return make_tl_object<telegram_api::sendMessageGamePlayAction>();
235     case Type::RecordingVideoNote:
236       return make_tl_object<telegram_api::sendMessageRecordRoundAction>();
237     case Type::UploadingVideoNote:
238       return make_tl_object<telegram_api::sendMessageUploadRoundAction>(progress_);
239     case Type::SpeakingInVoiceChat:
240       return make_tl_object<telegram_api::speakingInGroupCallAction>();
241     case Type::ImportingMessages:
242       return make_tl_object<telegram_api::sendMessageHistoryImportAction>(progress_);
243     case Type::ChoosingSticker:
244       return make_tl_object<telegram_api::sendMessageChooseStickerAction>();
245     case Type::WatchingAnimations:
246       return make_tl_object<telegram_api::sendMessageEmojiInteractionSeen>(emoji_);
247     case Type::ClickingAnimatedEmoji:
248     default:
249       UNREACHABLE();
250       return nullptr;
251   }
252 }
253 
get_secret_input_send_message_action() const254 tl_object_ptr<secret_api::SendMessageAction> DialogAction::get_secret_input_send_message_action() const {
255   switch (type_) {
256     case Type::Cancel:
257       return make_tl_object<secret_api::sendMessageCancelAction>();
258     case Type::Typing:
259       return make_tl_object<secret_api::sendMessageTypingAction>();
260     case Type::RecordingVideo:
261       return make_tl_object<secret_api::sendMessageRecordVideoAction>();
262     case Type::UploadingVideo:
263       return make_tl_object<secret_api::sendMessageUploadVideoAction>();
264     case Type::RecordingVoiceNote:
265       return make_tl_object<secret_api::sendMessageRecordAudioAction>();
266     case Type::UploadingVoiceNote:
267       return make_tl_object<secret_api::sendMessageUploadAudioAction>();
268     case Type::UploadingPhoto:
269       return make_tl_object<secret_api::sendMessageUploadPhotoAction>();
270     case Type::UploadingDocument:
271       return make_tl_object<secret_api::sendMessageUploadDocumentAction>();
272     case Type::ChoosingLocation:
273       return make_tl_object<secret_api::sendMessageGeoLocationAction>();
274     case Type::ChoosingContact:
275       return make_tl_object<secret_api::sendMessageChooseContactAction>();
276     case Type::StartPlayingGame:
277       return make_tl_object<secret_api::sendMessageTypingAction>();
278     case Type::RecordingVideoNote:
279       return make_tl_object<secret_api::sendMessageRecordRoundAction>();
280     case Type::UploadingVideoNote:
281       return make_tl_object<secret_api::sendMessageUploadRoundAction>();
282     case Type::SpeakingInVoiceChat:
283       return make_tl_object<secret_api::sendMessageTypingAction>();
284     case Type::ImportingMessages:
285       return make_tl_object<secret_api::sendMessageTypingAction>();
286     case Type::ChoosingSticker:
287       return make_tl_object<secret_api::sendMessageTypingAction>();
288     case Type::WatchingAnimations:
289       return make_tl_object<secret_api::sendMessageTypingAction>();
290     case Type::ClickingAnimatedEmoji:
291     default:
292       UNREACHABLE();
293       return nullptr;
294   }
295 }
296 
get_chat_action_object() const297 tl_object_ptr<td_api::ChatAction> DialogAction::get_chat_action_object() const {
298   switch (type_) {
299     case Type::Cancel:
300       return td_api::make_object<td_api::chatActionCancel>();
301     case Type::Typing:
302       return td_api::make_object<td_api::chatActionTyping>();
303     case Type::RecordingVideo:
304       return td_api::make_object<td_api::chatActionRecordingVideo>();
305     case Type::UploadingVideo:
306       return td_api::make_object<td_api::chatActionUploadingVideo>(progress_);
307     case Type::RecordingVoiceNote:
308       return td_api::make_object<td_api::chatActionRecordingVoiceNote>();
309     case Type::UploadingVoiceNote:
310       return td_api::make_object<td_api::chatActionUploadingVoiceNote>(progress_);
311     case Type::UploadingPhoto:
312       return td_api::make_object<td_api::chatActionUploadingPhoto>(progress_);
313     case Type::UploadingDocument:
314       return td_api::make_object<td_api::chatActionUploadingDocument>(progress_);
315     case Type::ChoosingLocation:
316       return td_api::make_object<td_api::chatActionChoosingLocation>();
317     case Type::ChoosingContact:
318       return td_api::make_object<td_api::chatActionChoosingContact>();
319     case Type::StartPlayingGame:
320       return td_api::make_object<td_api::chatActionStartPlayingGame>();
321     case Type::RecordingVideoNote:
322       return td_api::make_object<td_api::chatActionRecordingVideoNote>();
323     case Type::UploadingVideoNote:
324       return td_api::make_object<td_api::chatActionUploadingVideoNote>(progress_);
325     case Type::ChoosingSticker:
326       return td_api::make_object<td_api::chatActionChoosingSticker>();
327     case Type::WatchingAnimations:
328       return td_api::make_object<td_api::chatActionWatchingAnimations>(emoji_);
329     case Type::ImportingMessages:
330     case Type::SpeakingInVoiceChat:
331     case Type::ClickingAnimatedEmoji:
332     default:
333       UNREACHABLE();
334       return td_api::make_object<td_api::chatActionCancel>();
335   }
336 }
337 
is_canceled_by_message_of_type(MessageContentType message_content_type) const338 bool DialogAction::is_canceled_by_message_of_type(MessageContentType message_content_type) const {
339   if (message_content_type == MessageContentType::None) {
340     return true;
341   }
342 
343   if (type_ == Type::Typing) {
344     return message_content_type == MessageContentType::Text || message_content_type == MessageContentType::Game ||
345            can_have_message_content_caption(message_content_type);
346   }
347 
348   switch (message_content_type) {
349     case MessageContentType::Animation:
350     case MessageContentType::Audio:
351     case MessageContentType::Document:
352       return type_ == Type::UploadingDocument;
353     case MessageContentType::ExpiredPhoto:
354     case MessageContentType::Photo:
355       return type_ == Type::UploadingPhoto;
356     case MessageContentType::ExpiredVideo:
357     case MessageContentType::Video:
358       return type_ == Type::RecordingVideo || type_ == Type::UploadingVideo;
359     case MessageContentType::VideoNote:
360       return type_ == Type::RecordingVideoNote || type_ == Type::UploadingVideoNote;
361     case MessageContentType::VoiceNote:
362       return type_ == Type::RecordingVoiceNote || type_ == Type::UploadingVoiceNote;
363     case MessageContentType::Contact:
364       return type_ == Type::ChoosingContact;
365     case MessageContentType::LiveLocation:
366     case MessageContentType::Location:
367     case MessageContentType::Venue:
368       return type_ == Type::ChoosingLocation;
369     case MessageContentType::Sticker:
370       return type_ == Type::ChoosingSticker;
371     case MessageContentType::Game:
372     case MessageContentType::Invoice:
373     case MessageContentType::Text:
374     case MessageContentType::Unsupported:
375     case MessageContentType::ChatCreate:
376     case MessageContentType::ChatChangeTitle:
377     case MessageContentType::ChatChangePhoto:
378     case MessageContentType::ChatDeletePhoto:
379     case MessageContentType::ChatDeleteHistory:
380     case MessageContentType::ChatAddUsers:
381     case MessageContentType::ChatJoinedByLink:
382     case MessageContentType::ChatDeleteUser:
383     case MessageContentType::ChatMigrateTo:
384     case MessageContentType::ChannelCreate:
385     case MessageContentType::ChannelMigrateFrom:
386     case MessageContentType::PinMessage:
387     case MessageContentType::GameScore:
388     case MessageContentType::ScreenshotTaken:
389     case MessageContentType::ChatSetTtl:
390     case MessageContentType::Call:
391     case MessageContentType::PaymentSuccessful:
392     case MessageContentType::ContactRegistered:
393     case MessageContentType::CustomServiceAction:
394     case MessageContentType::WebsiteConnected:
395     case MessageContentType::PassportDataSent:
396     case MessageContentType::PassportDataReceived:
397     case MessageContentType::Poll:
398     case MessageContentType::Dice:
399     case MessageContentType::ProximityAlertTriggered:
400     case MessageContentType::GroupCall:
401     case MessageContentType::InviteToGroupCall:
402     case MessageContentType::ChatSetTheme:
403       return false;
404     default:
405       UNREACHABLE();
406       return false;
407   }
408 }
409 
get_uploading_action(MessageContentType message_content_type,int32 progress)410 DialogAction DialogAction::get_uploading_action(MessageContentType message_content_type, int32 progress) {
411   switch (message_content_type) {
412     case MessageContentType::Animation:
413     case MessageContentType::Audio:
414     case MessageContentType::Document:
415       return DialogAction(Type::UploadingDocument, progress);
416     case MessageContentType::Photo:
417       return DialogAction(Type::UploadingPhoto, progress);
418     case MessageContentType::Video:
419       return DialogAction(Type::UploadingVideo, progress);
420     case MessageContentType::VideoNote:
421       return DialogAction(Type::UploadingVideoNote, progress);
422     case MessageContentType::VoiceNote:
423       return DialogAction(Type::UploadingVoiceNote, progress);
424     default:
425       return DialogAction();
426   }
427 }
428 
get_typing_action()429 DialogAction DialogAction::get_typing_action() {
430   return DialogAction(Type::Typing, 0);
431 }
432 
get_speaking_action()433 DialogAction DialogAction::get_speaking_action() {
434   return DialogAction(Type::SpeakingInVoiceChat, 0);
435 }
436 
get_importing_messages_action_progress() const437 int32 DialogAction::get_importing_messages_action_progress() const {
438   if (type_ != Type::ImportingMessages) {
439     return -1;
440   }
441   return progress_;
442 }
443 
get_watching_animations_emoji() const444 string DialogAction::get_watching_animations_emoji() const {
445   if (type_ == Type::WatchingAnimations) {
446     return emoji_;
447   }
448   return string();
449 }
450 
get_clicking_animated_emoji_action_info() const451 DialogAction::ClickingAnimateEmojiInfo DialogAction::get_clicking_animated_emoji_action_info() const {
452   ClickingAnimateEmojiInfo result;
453   if (type_ == Type::ClickingAnimatedEmoji) {
454     auto pos = emoji_.find('\xFF');
455     CHECK(pos < emoji_.size());
456     result.message_id = progress_;
457     result.emoji = emoji_.substr(0, pos);
458     result.data = emoji_.substr(pos + 1);
459   }
460   return result;
461 }
462 
operator <<(StringBuilder & string_builder,const DialogAction & action)463 StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &action) {
464   string_builder << "ChatAction";
465   const char *type = [action_type = action.type_] {
466     switch (action_type) {
467       case DialogAction::Type::Cancel:
468         return "Cancel";
469       case DialogAction::Type::Typing:
470         return "Typing";
471       case DialogAction::Type::RecordingVideo:
472         return "RecordingVideo";
473       case DialogAction::Type::UploadingVideo:
474         return "UploadingVideo";
475       case DialogAction::Type::RecordingVoiceNote:
476         return "RecordingVoiceNote";
477       case DialogAction::Type::UploadingVoiceNote:
478         return "UploadingVoiceNote";
479       case DialogAction::Type::UploadingPhoto:
480         return "UploadingPhoto";
481       case DialogAction::Type::UploadingDocument:
482         return "UploadingDocument";
483       case DialogAction::Type::ChoosingLocation:
484         return "ChoosingLocation";
485       case DialogAction::Type::ChoosingContact:
486         return "ChoosingContact";
487       case DialogAction::Type::StartPlayingGame:
488         return "StartPlayingGame";
489       case DialogAction::Type::RecordingVideoNote:
490         return "RecordingVideoNote";
491       case DialogAction::Type::UploadingVideoNote:
492         return "UploadingVideoNote";
493       case DialogAction::Type::SpeakingInVoiceChat:
494         return "SpeakingInVoiceChat";
495       case DialogAction::Type::ImportingMessages:
496         return "ImportingMessages";
497       case DialogAction::Type::ChoosingSticker:
498         return "ChoosingSticker";
499       case DialogAction::Type::WatchingAnimations:
500         return "WatchingAnimations";
501       case DialogAction::Type::ClickingAnimatedEmoji:
502         return "ClickingAnimatedEmoji";
503       default:
504         UNREACHABLE();
505         return "Cancel";
506     }
507   }();
508   string_builder << type << "Action";
509   if (action.type_ == DialogAction::Type::ClickingAnimatedEmoji) {
510     auto pos = action.emoji_.find('\xFF');
511     CHECK(pos < action.emoji_.size());
512     string_builder << '(' << action.progress_ << ")(" << Slice(action.emoji_).substr(0, pos) << ")("
513                    << Slice(action.emoji_).substr(pos + 1) << ')';
514   } else {
515     if (action.progress_ != 0) {
516       string_builder << '(' << action.progress_ << "%)";
517     }
518     if (!action.emoji_.empty()) {
519       string_builder << '(' << action.emoji_ << ')';
520     }
521   }
522   return string_builder;
523 }
524 
525 }  // namespace td
526