289 lines
8.5 KiB
C++
289 lines
8.5 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "data/business/data_business_common.h"
|
|
|
|
#include "data/data_session.h"
|
|
#include "data/data_user.h"
|
|
|
|
namespace Data {
|
|
namespace {
|
|
|
|
constexpr auto kDay = WorkingInterval::kDay;
|
|
constexpr auto kWeek = WorkingInterval::kWeek;
|
|
constexpr auto kInNextDayMax = WorkingInterval::kInNextDayMax;
|
|
|
|
[[nodiscard]] WorkingIntervals SortAndMerge(WorkingIntervals intervals) {
|
|
auto &list = intervals.list;
|
|
ranges::sort(list, ranges::less(), &WorkingInterval::start);
|
|
for (auto i = 0, count = int(list.size()); i != count; ++i) {
|
|
if (i && list[i] && list[i -1] && list[i].start <= list[i - 1].end) {
|
|
list[i - 1] = list[i - 1].united(list[i]);
|
|
list[i] = {};
|
|
}
|
|
if (!list[i]) {
|
|
list.erase(list.begin() + i);
|
|
--i;
|
|
--count;
|
|
}
|
|
}
|
|
return intervals;
|
|
}
|
|
|
|
[[nodiscard]] WorkingIntervals MoveTailToFront(WorkingIntervals intervals) {
|
|
auto &list = intervals.list;
|
|
auto after = WorkingInterval{ kWeek, kWeek + kDay };
|
|
while (!list.empty()) {
|
|
if (const auto tail = list.back().intersected(after)) {
|
|
list.back().end = tail.start;
|
|
if (!list.back()) {
|
|
list.pop_back();
|
|
}
|
|
list.insert(begin(list), tail.shifted(-kWeek));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return intervals;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MTPInputBusinessRecipients ToMTP(
|
|
const BusinessRecipients &data) {
|
|
using Flag = MTPDinputBusinessRecipients::Flag;
|
|
using Type = BusinessChatType;
|
|
const auto &chats = data.allButExcluded
|
|
? data.excluded
|
|
: data.included;
|
|
const auto flags = Flag()
|
|
| ((chats.types & Type::NewChats) ? Flag::f_new_chats : Flag())
|
|
| ((chats.types & Type::ExistingChats)
|
|
? Flag::f_existing_chats
|
|
: Flag())
|
|
| ((chats.types & Type::Contacts) ? Flag::f_contacts : Flag())
|
|
| ((chats.types & Type::NonContacts) ? Flag::f_non_contacts : Flag())
|
|
| (chats.list.empty() ? Flag() : Flag::f_users)
|
|
| (data.allButExcluded ? Flag::f_exclude_selected : Flag());
|
|
const auto &users = data.allButExcluded
|
|
? data.excluded
|
|
: data.included;
|
|
return MTP_inputBusinessRecipients(
|
|
MTP_flags(flags),
|
|
MTP_vector_from_range(users.list
|
|
| ranges::views::transform(&UserData::inputUser)));
|
|
}
|
|
|
|
BusinessRecipients FromMTP(
|
|
not_null<Session*> owner,
|
|
const MTPBusinessRecipients &recipients) {
|
|
using Type = BusinessChatType;
|
|
|
|
const auto &data = recipients.data();
|
|
auto result = BusinessRecipients{
|
|
.allButExcluded = data.is_exclude_selected(),
|
|
};
|
|
auto &chats = result.allButExcluded
|
|
? result.excluded
|
|
: result.included;
|
|
chats.types = Type()
|
|
| (data.is_new_chats() ? Type::NewChats : Type())
|
|
| (data.is_existing_chats() ? Type::ExistingChats : Type())
|
|
| (data.is_contacts() ? Type::Contacts : Type())
|
|
| (data.is_non_contacts() ? Type::NonContacts : Type());
|
|
if (const auto users = data.vusers()) {
|
|
for (const auto &userId : users->v) {
|
|
chats.list.push_back(owner->user(UserId(userId.v)));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] BusinessDetails FromMTP(
|
|
const tl::conditional<MTPBusinessWorkHours> &hours,
|
|
const tl::conditional<MTPBusinessLocation> &location) {
|
|
auto result = BusinessDetails();
|
|
if (hours) {
|
|
const auto &data = hours->data();
|
|
result.hours.timezoneId = qs(data.vtimezone_id());
|
|
result.hours.intervals.list = ranges::views::all(
|
|
data.vweekly_open().v
|
|
) | ranges::views::transform([](const MTPBusinessWeeklyOpen &open) {
|
|
const auto &data = open.data();
|
|
return WorkingInterval{
|
|
data.vstart_minute().v * 60,
|
|
data.vend_minute().v * 60,
|
|
};
|
|
}) | ranges::to_vector;
|
|
}
|
|
if (location) {
|
|
const auto &data = location->data();
|
|
result.location.address = qs(data.vaddress());
|
|
if (const auto point = data.vgeo_point()) {
|
|
point->match([&](const MTPDgeoPoint &data) {
|
|
result.location.point = LocationPoint(data);
|
|
}, [&](const MTPDgeoPointEmpty &) {
|
|
});
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] AwaySettings FromMTP(
|
|
not_null<Session*> owner,
|
|
const tl::conditional<MTPBusinessAwayMessage> &message) {
|
|
if (!message) {
|
|
return AwaySettings();
|
|
}
|
|
const auto &data = message->data();
|
|
auto result = AwaySettings{
|
|
.recipients = FromMTP(owner, data.vrecipients()),
|
|
.shortcutId = data.vshortcut_id().v,
|
|
.offlineOnly = data.is_offline_only(),
|
|
};
|
|
data.vschedule().match([&](
|
|
const MTPDbusinessAwayMessageScheduleAlways &) {
|
|
result.schedule.type = AwayScheduleType::Always;
|
|
}, [&](const MTPDbusinessAwayMessageScheduleOutsideWorkHours &) {
|
|
result.schedule.type = AwayScheduleType::OutsideWorkingHours;
|
|
}, [&](const MTPDbusinessAwayMessageScheduleCustom &data) {
|
|
result.schedule.type = AwayScheduleType::Custom;
|
|
result.schedule.customInterval = WorkingInterval{
|
|
data.vstart_date().v,
|
|
data.vend_date().v,
|
|
};
|
|
});
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] GreetingSettings FromMTP(
|
|
not_null<Session*> owner,
|
|
const tl::conditional<MTPBusinessGreetingMessage> &message) {
|
|
if (!message) {
|
|
return GreetingSettings();
|
|
}
|
|
const auto &data = message->data();
|
|
return GreetingSettings{
|
|
.recipients = FromMTP(owner, data.vrecipients()),
|
|
.noActivityDays = data.vno_activity_days().v,
|
|
.shortcutId = data.vshortcut_id().v,
|
|
};
|
|
}
|
|
|
|
WorkingIntervals WorkingIntervals::normalized() const {
|
|
return SortAndMerge(MoveTailToFront(SortAndMerge(*this)));
|
|
}
|
|
|
|
WorkingIntervals ExtractDayIntervals(
|
|
const WorkingIntervals &intervals,
|
|
int dayIndex) {
|
|
Expects(dayIndex >= 0 && dayIndex < 7);
|
|
|
|
auto result = WorkingIntervals();
|
|
auto &list = result.list;
|
|
for (const auto &interval : intervals.list) {
|
|
const auto now = interval.intersected(
|
|
{ (dayIndex - 1) * kDay, (dayIndex + 2) * kDay });
|
|
const auto after = interval.intersected(
|
|
{ (dayIndex + 6) * kDay, (dayIndex + 9) * kDay });
|
|
const auto before = interval.intersected(
|
|
{ (dayIndex - 8) * kDay, (dayIndex - 5) * kDay });
|
|
if (now) {
|
|
list.push_back(now.shifted(-dayIndex * kDay));
|
|
}
|
|
if (after) {
|
|
list.push_back(after.shifted(-(dayIndex + 7) * kDay));
|
|
}
|
|
if (before) {
|
|
list.push_back(before.shifted(-(dayIndex - 7) * kDay));
|
|
}
|
|
}
|
|
result = result.normalized();
|
|
|
|
const auto outside = [&](WorkingInterval interval) {
|
|
return (interval.end <= 0) || (interval.start >= kDay);
|
|
};
|
|
list.erase(ranges::remove_if(list, outside), end(list));
|
|
|
|
if (!list.empty() && list.back().start <= 0 && list.back().end >= kDay) {
|
|
list.back() = { 0, kDay };
|
|
} else if (!list.empty() && (list.back().end > kDay + kInNextDayMax)) {
|
|
list.back() = list.back().intersected({ 0, kDay });
|
|
}
|
|
if (!list.empty() && list.front().start <= 0) {
|
|
if (list.front().start < 0
|
|
&& list.front().end <= kInNextDayMax
|
|
&& list.front().start > -kDay) {
|
|
list.erase(begin(list));
|
|
} else {
|
|
list.front() = list.front().intersected({ 0, kDay });
|
|
if (!list.front()) {
|
|
list.erase(begin(list));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool IsFullOpen(const WorkingIntervals &extractedDay) {
|
|
return extractedDay // 00:00-23:59 or 00:00-00:00 (next day)
|
|
&& (extractedDay.list.front() == WorkingInterval{ 0, kDay - 60 }
|
|
|| extractedDay.list.front() == WorkingInterval{ 0, kDay });
|
|
}
|
|
|
|
WorkingIntervals RemoveDayIntervals(
|
|
const WorkingIntervals &intervals,
|
|
int dayIndex) {
|
|
auto result = intervals.normalized();
|
|
auto &list = result.list;
|
|
const auto day = WorkingInterval{ 0, kDay };
|
|
const auto shifted = day.shifted(dayIndex * kDay);
|
|
auto before = WorkingInterval{ 0, shifted.start };
|
|
auto after = WorkingInterval{ shifted.end, kWeek };
|
|
for (auto i = 0, count = int(list.size()); i != count; ++i) {
|
|
if (list[i].end <= shifted.start || list[i].start >= shifted.end) {
|
|
continue;
|
|
} else if (list[i].end <= shifted.start + kInNextDayMax
|
|
&& (list[i].start < shifted.start
|
|
|| (!dayIndex // This 'Sunday' finishing on next day <= 6:00.
|
|
&& list[i].start == shifted.start
|
|
&& list.back().end >= kWeek))) {
|
|
continue;
|
|
} else if (const auto first = list[i].intersected(before)) {
|
|
list[i] = first;
|
|
if (const auto second = list[i].intersected(after)) {
|
|
list.push_back(second);
|
|
}
|
|
} else if (const auto second = list[i].intersected(after)) {
|
|
list[i] = second;
|
|
} else {
|
|
list.erase(list.begin() + i);
|
|
--i;
|
|
--count;
|
|
}
|
|
}
|
|
return result.normalized();
|
|
}
|
|
|
|
WorkingIntervals ReplaceDayIntervals(
|
|
const WorkingIntervals &intervals,
|
|
int dayIndex,
|
|
WorkingIntervals replacement) {
|
|
auto result = RemoveDayIntervals(intervals, dayIndex);
|
|
const auto first = result.list.insert(
|
|
end(result.list),
|
|
begin(replacement.list),
|
|
end(replacement.list));
|
|
for (auto &interval : ranges::make_subrange(first, end(result.list))) {
|
|
interval = interval.shifted(dayIndex * kDay);
|
|
}
|
|
return result.normalized();
|
|
}
|
|
|
|
} // namespace Data
|