data_business_common.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "data/business/data_business_common.h"
  8. #include "data/data_document.h"
  9. #include "data/data_session.h"
  10. #include "data/data_user.h"
  11. namespace Data {
  12. namespace {
  13. constexpr auto kDay = WorkingInterval::kDay;
  14. constexpr auto kWeek = WorkingInterval::kWeek;
  15. constexpr auto kInNextDayMax = WorkingInterval::kInNextDayMax;
  16. [[nodiscard]] WorkingIntervals SortAndMerge(WorkingIntervals intervals) {
  17. auto &list = intervals.list;
  18. ranges::sort(list, ranges::less(), &WorkingInterval::start);
  19. for (auto i = 0, count = int(list.size()); i != count; ++i) {
  20. if (i && list[i] && list[i -1] && list[i].start <= list[i - 1].end) {
  21. list[i - 1] = list[i - 1].united(list[i]);
  22. list[i] = {};
  23. }
  24. if (!list[i]) {
  25. list.erase(list.begin() + i);
  26. --i;
  27. --count;
  28. }
  29. }
  30. return intervals;
  31. }
  32. [[nodiscard]] WorkingIntervals MoveTailToFront(WorkingIntervals intervals) {
  33. auto &list = intervals.list;
  34. auto after = WorkingInterval{ kWeek, kWeek + kDay };
  35. while (!list.empty()) {
  36. if (const auto tail = list.back().intersected(after)) {
  37. list.back().end = tail.start;
  38. if (!list.back()) {
  39. list.pop_back();
  40. }
  41. list.insert(begin(list), tail.shifted(-kWeek));
  42. } else {
  43. break;
  44. }
  45. }
  46. return intervals;
  47. }
  48. template <typename Flag>
  49. auto RecipientsFlags(const BusinessRecipients &data) {
  50. const auto &chats = data.allButExcluded
  51. ? data.excluded
  52. : data.included;
  53. using Type = BusinessChatType;
  54. return Flag()
  55. | ((chats.types & Type::NewChats) ? Flag::f_new_chats : Flag())
  56. | ((chats.types & Type::ExistingChats)
  57. ? Flag::f_existing_chats
  58. : Flag())
  59. | ((chats.types & Type::Contacts) ? Flag::f_contacts : Flag())
  60. | ((chats.types & Type::NonContacts) ? Flag::f_non_contacts : Flag())
  61. | (chats.list.empty() ? Flag() : Flag::f_users)
  62. | (data.allButExcluded ? Flag::f_exclude_selected : Flag());
  63. }
  64. } // namespace
  65. BusinessRecipients BusinessRecipients::MakeValid(BusinessRecipients value) {
  66. if (value.included.empty()) {
  67. value.allButExcluded = true;
  68. }
  69. return value;
  70. }
  71. MTPInputBusinessRecipients ForMessagesToMTP(const BusinessRecipients &data) {
  72. using Flag = MTPDinputBusinessRecipients::Flag;
  73. const auto &chats = data.allButExcluded ? data.excluded : data.included;
  74. return MTP_inputBusinessRecipients(
  75. MTP_flags(RecipientsFlags<Flag>(data)),
  76. MTP_vector_from_range(chats.list
  77. | ranges::views::transform(&UserData::inputUser)));
  78. }
  79. MTPInputBusinessBotRecipients ForBotsToMTP(const BusinessRecipients &data) {
  80. using Flag = MTPDinputBusinessBotRecipients::Flag;
  81. const auto &chats = data.allButExcluded ? data.excluded : data.included;
  82. return MTP_inputBusinessBotRecipients(
  83. MTP_flags(RecipientsFlags<Flag>(data)
  84. | ((data.allButExcluded || data.excluded.empty())
  85. ? Flag()
  86. : Flag::f_exclude_users)),
  87. MTP_vector_from_range(chats.list
  88. | ranges::views::transform(&UserData::inputUser)),
  89. MTP_vector_from_range(data.excluded.list
  90. | ranges::views::transform(&UserData::inputUser)));
  91. }
  92. BusinessRecipients FromMTP(
  93. not_null<Session*> owner,
  94. const MTPBusinessRecipients &recipients) {
  95. using Type = BusinessChatType;
  96. const auto &data = recipients.data();
  97. auto result = BusinessRecipients{
  98. .allButExcluded = data.is_exclude_selected(),
  99. };
  100. auto &chats = result.allButExcluded
  101. ? result.excluded
  102. : result.included;
  103. chats.types = Type()
  104. | (data.is_new_chats() ? Type::NewChats : Type())
  105. | (data.is_existing_chats() ? Type::ExistingChats : Type())
  106. | (data.is_contacts() ? Type::Contacts : Type())
  107. | (data.is_non_contacts() ? Type::NonContacts : Type());
  108. if (const auto users = data.vusers()) {
  109. for (const auto &userId : users->v) {
  110. chats.list.push_back(owner->user(UserId(userId.v)));
  111. }
  112. }
  113. return result;
  114. }
  115. BusinessRecipients FromMTP(
  116. not_null<Session*> owner,
  117. const MTPBusinessBotRecipients &recipients) {
  118. using Type = BusinessChatType;
  119. const auto &data = recipients.data();
  120. auto result = BusinessRecipients{
  121. .allButExcluded = data.is_exclude_selected(),
  122. };
  123. auto &chats = result.allButExcluded
  124. ? result.excluded
  125. : result.included;
  126. chats.types = Type()
  127. | (data.is_new_chats() ? Type::NewChats : Type())
  128. | (data.is_existing_chats() ? Type::ExistingChats : Type())
  129. | (data.is_contacts() ? Type::Contacts : Type())
  130. | (data.is_non_contacts() ? Type::NonContacts : Type());
  131. if (const auto users = data.vusers()) {
  132. for (const auto &userId : users->v) {
  133. chats.list.push_back(owner->user(UserId(userId.v)));
  134. }
  135. }
  136. if (!result.allButExcluded) {
  137. if (const auto excluded = data.vexclude_users()) {
  138. for (const auto &userId : excluded->v) {
  139. result.excluded.list.push_back(
  140. owner->user(UserId(userId.v)));
  141. }
  142. }
  143. }
  144. return result;
  145. }
  146. BusinessDetails FromMTP(
  147. not_null<Session*> owner,
  148. const tl::conditional<MTPBusinessWorkHours> &hours,
  149. const tl::conditional<MTPBusinessLocation> &location,
  150. const tl::conditional<MTPBusinessIntro> &intro) {
  151. auto result = BusinessDetails();
  152. if (hours) {
  153. const auto &data = hours->data();
  154. result.hours.timezoneId = qs(data.vtimezone_id());
  155. result.hours.intervals.list = ranges::views::all(
  156. data.vweekly_open().v
  157. ) | ranges::views::transform([](const MTPBusinessWeeklyOpen &open) {
  158. const auto &data = open.data();
  159. return WorkingInterval{
  160. data.vstart_minute().v * 60,
  161. data.vend_minute().v * 60,
  162. };
  163. }) | ranges::to_vector;
  164. }
  165. if (location) {
  166. const auto &data = location->data();
  167. result.location.address = qs(data.vaddress());
  168. if (const auto point = data.vgeo_point()) {
  169. point->match([&](const MTPDgeoPoint &data) {
  170. result.location.point = LocationPoint(data);
  171. }, [&](const MTPDgeoPointEmpty &) {
  172. });
  173. }
  174. }
  175. if (intro) {
  176. const auto &data = intro->data();
  177. result.intro.title = qs(data.vtitle());
  178. result.intro.description = qs(data.vdescription());
  179. if (const auto document = data.vsticker()) {
  180. result.intro.sticker = owner->processDocument(*document);
  181. if (!result.intro.sticker->sticker()) {
  182. result.intro.sticker = nullptr;
  183. }
  184. }
  185. }
  186. return result;
  187. }
  188. [[nodiscard]] AwaySettings FromMTP(
  189. not_null<Session*> owner,
  190. const tl::conditional<MTPBusinessAwayMessage> &message) {
  191. if (!message) {
  192. return AwaySettings();
  193. }
  194. const auto &data = message->data();
  195. auto result = AwaySettings{
  196. .recipients = FromMTP(owner, data.vrecipients()),
  197. .shortcutId = data.vshortcut_id().v,
  198. .offlineOnly = data.is_offline_only(),
  199. };
  200. data.vschedule().match([&](
  201. const MTPDbusinessAwayMessageScheduleAlways &) {
  202. result.schedule.type = AwayScheduleType::Always;
  203. }, [&](const MTPDbusinessAwayMessageScheduleOutsideWorkHours &) {
  204. result.schedule.type = AwayScheduleType::OutsideWorkingHours;
  205. }, [&](const MTPDbusinessAwayMessageScheduleCustom &data) {
  206. result.schedule.type = AwayScheduleType::Custom;
  207. result.schedule.customInterval = WorkingInterval{
  208. data.vstart_date().v,
  209. data.vend_date().v,
  210. };
  211. });
  212. return result;
  213. }
  214. [[nodiscard]] GreetingSettings FromMTP(
  215. not_null<Session*> owner,
  216. const tl::conditional<MTPBusinessGreetingMessage> &message) {
  217. if (!message) {
  218. return GreetingSettings();
  219. }
  220. const auto &data = message->data();
  221. return GreetingSettings{
  222. .recipients = FromMTP(owner, data.vrecipients()),
  223. .noActivityDays = data.vno_activity_days().v,
  224. .shortcutId = data.vshortcut_id().v,
  225. };
  226. }
  227. WorkingIntervals WorkingIntervals::normalized() const {
  228. return SortAndMerge(MoveTailToFront(SortAndMerge(*this)));
  229. }
  230. WorkingIntervals ExtractDayIntervals(
  231. const WorkingIntervals &intervals,
  232. int dayIndex) {
  233. Expects(dayIndex >= 0 && dayIndex < 7);
  234. auto result = WorkingIntervals();
  235. auto &list = result.list;
  236. for (const auto &interval : intervals.list) {
  237. const auto now = interval.intersected(
  238. { (dayIndex - 1) * kDay, (dayIndex + 2) * kDay });
  239. const auto after = interval.intersected(
  240. { (dayIndex + 6) * kDay, (dayIndex + 9) * kDay });
  241. const auto before = interval.intersected(
  242. { (dayIndex - 8) * kDay, (dayIndex - 5) * kDay });
  243. if (now) {
  244. list.push_back(now.shifted(-dayIndex * kDay));
  245. }
  246. if (after) {
  247. list.push_back(after.shifted(-(dayIndex + 7) * kDay));
  248. }
  249. if (before) {
  250. list.push_back(before.shifted(-(dayIndex - 7) * kDay));
  251. }
  252. }
  253. result = result.normalized();
  254. const auto outside = [&](WorkingInterval interval) {
  255. return (interval.end <= 0) || (interval.start >= kDay);
  256. };
  257. list.erase(ranges::remove_if(list, outside), end(list));
  258. if (!list.empty() && list.back().start <= 0 && list.back().end >= kDay) {
  259. list.back() = { 0, kDay };
  260. } else if (!list.empty() && (list.back().end > kDay + kInNextDayMax)) {
  261. list.back() = list.back().intersected({ 0, kDay });
  262. }
  263. if (!list.empty() && list.front().start <= 0) {
  264. if (list.front().start < 0
  265. && list.front().end <= kInNextDayMax
  266. && list.front().start > -kDay) {
  267. list.erase(begin(list));
  268. } else {
  269. list.front() = list.front().intersected({ 0, kDay });
  270. if (!list.front()) {
  271. list.erase(begin(list));
  272. }
  273. }
  274. }
  275. return result;
  276. }
  277. bool IsFullOpen(const WorkingIntervals &extractedDay) {
  278. return extractedDay // 00:00-23:59 or 00:00-00:00 (next day)
  279. && (extractedDay.list.front() == WorkingInterval{ 0, kDay - 60 }
  280. || extractedDay.list.front() == WorkingInterval{ 0, kDay });
  281. }
  282. WorkingIntervals RemoveDayIntervals(
  283. const WorkingIntervals &intervals,
  284. int dayIndex) {
  285. auto result = intervals.normalized();
  286. auto &list = result.list;
  287. const auto day = WorkingInterval{ 0, kDay };
  288. const auto shifted = day.shifted(dayIndex * kDay);
  289. auto before = WorkingInterval{ 0, shifted.start };
  290. auto after = WorkingInterval{ shifted.end, kWeek };
  291. for (auto i = 0, count = int(list.size()); i != count; ++i) {
  292. if (list[i].end <= shifted.start || list[i].start >= shifted.end) {
  293. continue;
  294. } else if (list[i].end <= shifted.start + kInNextDayMax
  295. && (list[i].start < shifted.start
  296. || (!dayIndex // This 'Sunday' finishing on next day <= 6:00.
  297. && list[i].start == shifted.start
  298. && list.back().end >= kWeek))) {
  299. continue;
  300. } else if (const auto first = list[i].intersected(before)) {
  301. list[i] = first;
  302. if (const auto second = list[i].intersected(after)) {
  303. list.push_back(second);
  304. }
  305. } else if (const auto second = list[i].intersected(after)) {
  306. list[i] = second;
  307. } else {
  308. list.erase(list.begin() + i);
  309. --i;
  310. --count;
  311. }
  312. }
  313. return result.normalized();
  314. }
  315. WorkingIntervals ReplaceDayIntervals(
  316. const WorkingIntervals &intervals,
  317. int dayIndex,
  318. WorkingIntervals replacement) {
  319. auto result = RemoveDayIntervals(intervals, dayIndex);
  320. const auto first = result.list.insert(
  321. end(result.list),
  322. begin(replacement.list),
  323. end(replacement.list));
  324. for (auto &interval : ranges::make_subrange(first, end(result.list))) {
  325. interval = interval.shifted(dayIndex * kDay);
  326. }
  327. return result.normalized();
  328. }
  329. } // namespace Data