profile_block_group_members.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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 "profile/profile_block_group_members.h"
  8. #include "api/api_chat_participants.h"
  9. #include "styles/style_profile.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/boxes/confirm_box.h"
  12. #include "boxes/peers/edit_participant_box.h"
  13. #include "boxes/peers/edit_participants_box.h"
  14. #include "base/unixtime.h"
  15. #include "ui/widgets/popup_menu.h"
  16. #include "mtproto/mtproto_config.h"
  17. #include "data/data_peer_values.h"
  18. #include "data/data_channel.h"
  19. #include "data/data_chat.h"
  20. #include "data/data_user.h"
  21. #include "data/data_changes.h"
  22. #include "mainwidget.h"
  23. #include "apiwrap.h"
  24. #include "main/main_session.h"
  25. #include "lang/lang_keys.h"
  26. #include "window/window_session_controller.h"
  27. namespace Profile {
  28. namespace {
  29. using UpdateFlag = Data::PeerUpdate::Flag;
  30. } // namespace
  31. GroupMembersWidget::Member::Member(not_null<UserData*> user) : Item(user) {
  32. }
  33. not_null<UserData*> GroupMembersWidget::Member::user() const {
  34. return static_cast<UserData*>(peer.get());
  35. }
  36. GroupMembersWidget::GroupMembersWidget(
  37. QWidget *parent,
  38. not_null<Window::SessionController*> controller,
  39. not_null<PeerData*> peer,
  40. const style::PeerListItem &st)
  41. : PeerListWidget(parent, peer, QString(), st, tr::lng_profile_kick(tr::now))
  42. , _controller(controller)
  43. , _updateOnlineTimer([=] { updateOnlineDisplay(); }) {
  44. peer->session().changes().peerUpdates(
  45. UpdateFlag::Admins
  46. | UpdateFlag::Members
  47. | UpdateFlag::OnlineStatus
  48. ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
  49. notifyPeerUpdated(update);
  50. }, lifetime());
  51. setRemovedCallback([=](PeerData *selectedPeer) {
  52. removePeer(selectedPeer);
  53. });
  54. setSelectedCallback([=](PeerData *selectedPeer) {
  55. controller->showPeerInfo(selectedPeer);
  56. });
  57. setUpdateItemCallback([=](Item *item) {
  58. updateItemStatusText(item);
  59. });
  60. setPreloadMoreCallback([=] {
  61. preloadMore();
  62. });
  63. refreshMembers();
  64. }
  65. void GroupMembersWidget::removePeer(PeerData *selectedPeer) {
  66. const auto user = selectedPeer->asUser();
  67. Assert(user != nullptr);
  68. const auto text = tr::lng_profile_sure_kick(
  69. tr::now,
  70. lt_user, user->firstName);
  71. const auto currentRestrictedRights = [&]() -> ChatRestrictionsInfo {
  72. if (auto channel = peer()->asMegagroup()) {
  73. auto it = channel->mgInfo->lastRestricted.find(user);
  74. if (it != channel->mgInfo->lastRestricted.cend()) {
  75. return it->second.rights;
  76. }
  77. }
  78. return ChatRestrictionsInfo();
  79. }();
  80. const auto peer = this->peer();
  81. const auto callback = [=, controller = _controller] {
  82. controller->hideLayer();
  83. if (const auto chat = peer->asChat()) {
  84. chat->session().api().chatParticipants().kick(chat, user);
  85. controller->showPeerHistory(
  86. chat->id,
  87. Window::SectionShow::Way::ClearStack,
  88. ShowAtTheEndMsgId);
  89. } else if (const auto channel = peer->asChannel()) {
  90. channel->session().api().chatParticipants().kick(
  91. channel,
  92. user,
  93. currentRestrictedRights);
  94. }
  95. };
  96. _controller->show(Ui::MakeConfirmBox({
  97. .text = text,
  98. .confirmed = crl::guard(&peer->session(), callback),
  99. .confirmText = tr::lng_box_remove(),
  100. }));
  101. }
  102. void GroupMembersWidget::notifyPeerUpdated(const Data::PeerUpdate &update) {
  103. if (update.peer != peer()) {
  104. if (update.flags & UpdateFlag::OnlineStatus) {
  105. if (auto user = update.peer->asUser()) {
  106. refreshUserOnline(user);
  107. }
  108. }
  109. return;
  110. }
  111. if (update.flags & UpdateFlag::Members) {
  112. refreshMembers();
  113. contentSizeUpdated();
  114. }
  115. if (update.flags & UpdateFlag::Admins) {
  116. if (const auto chat = peer()->asChat()) {
  117. for (const auto item : items()) {
  118. setItemFlags(getMember(item), chat);
  119. }
  120. } else if (const auto megagroup = peer()->asMegagroup()) {
  121. for (const auto item : items()) {
  122. setItemFlags(getMember(item), megagroup);
  123. }
  124. }
  125. }
  126. this->update();
  127. }
  128. void GroupMembersWidget::refreshUserOnline(UserData *user) {
  129. auto it = _membersByUser.find(user);
  130. if (it == _membersByUser.cend()) return;
  131. _now = base::unixtime::now();
  132. auto member = getMember(it->second);
  133. member->lastseen = user->lastseen();
  134. member->statusHasOnlineColor = Data::OnlineTextActive(user, _now);
  135. member->onlineForSort = user->isSelf()
  136. ? std::numeric_limits<TimeId>::max()
  137. : Data::SortByOnlineValue(user, _now);
  138. member->statusText = QString();
  139. sortMembers();
  140. update();
  141. }
  142. void GroupMembersWidget::preloadMore() {
  143. //
  144. // This can cause a ddos, because lastParticipants may never reach members count.
  145. //
  146. //if (auto megagroup = peer()->asMegagroup()) {
  147. // auto &megagroupInfo = megagroup->mgInfo;
  148. // if (!megagroupInfo->lastParticipants.isEmpty() && megagroupInfo->lastParticipants.size() < megagroup->membersCount()) {
  149. // peer()->session().api().requestLast(megagroup, false);
  150. // }
  151. //}
  152. }
  153. void GroupMembersWidget::updateItemStatusText(Item *item) {
  154. auto member = getMember(item);
  155. auto user = member->user();
  156. if (member->statusText.isEmpty() || (member->onlineTextTill <= _now)) {
  157. if (user->isBot()) {
  158. const auto seesAllMessages = user->botInfo->readsAllHistory
  159. || member->rank.has_value();
  160. member->statusText = seesAllMessages
  161. ? tr::lng_status_bot_reads_all(tr::now)
  162. : tr::lng_status_bot_not_reads_all(tr::now);
  163. member->onlineTextTill = _now + 86400;
  164. } else {
  165. member->statusHasOnlineColor = member->lastseen.isOnline(_now);
  166. member->statusText = Data::OnlineText(member->lastseen, _now);
  167. const auto changeInMs = Data::OnlineChangeTimeout(
  168. member->lastseen,
  169. _now);
  170. member->onlineTextTill = _now + TimeId(changeInMs / 1000);
  171. }
  172. }
  173. if (_updateOnlineAt <= _now || _updateOnlineAt > member->onlineTextTill) {
  174. _updateOnlineAt = member->onlineTextTill;
  175. _updateOnlineTimer.callOnce((_updateOnlineAt - _now + 1) * 1000);
  176. }
  177. }
  178. void GroupMembersWidget::refreshMembers() {
  179. _now = base::unixtime::now();
  180. if (const auto chat = peer()->asChat()) {
  181. checkSelfAdmin(chat);
  182. if (chat->noParticipantInfo()) {
  183. chat->session().api().requestFullPeer(chat);
  184. }
  185. fillChatMembers(chat);
  186. } else if (const auto megagroup = peer()->asMegagroup()) {
  187. if (megagroup->lastParticipantsRequestNeeded()) {
  188. megagroup->session().api().chatParticipants().requestLast(
  189. megagroup);
  190. }
  191. fillMegagroupMembers(megagroup);
  192. }
  193. sortMembers();
  194. refreshVisibility();
  195. }
  196. void GroupMembersWidget::checkSelfAdmin(not_null<ChatData*> chat) {
  197. if (chat->participants.empty()) {
  198. return;
  199. }
  200. //const auto self = chat->session().user();
  201. //if (chat->amAdmin() && !chat->admins.contains(self)) {
  202. // chat->admins.insert(self);
  203. //} else if (!chat->amAdmin() && chat->admins.contains(self)) {
  204. // chat->admins.remove(self);
  205. //}
  206. }
  207. void GroupMembersWidget::sortMembers() {
  208. if (!_sortByOnline || !itemsCount()) return;
  209. sortItems([this](Item *a, Item *b) {
  210. return getMember(a)->onlineForSort > getMember(b)->onlineForSort;
  211. });
  212. updateOnlineCount();
  213. }
  214. void GroupMembersWidget::updateOnlineCount() {
  215. bool onlyMe = true;
  216. int newOnlineCount = 0;
  217. for (const auto item : items()) {
  218. auto member = getMember(item);
  219. auto user = member->user();
  220. auto isOnline = !user->isBot()
  221. && member->lastseen.isOnline(_now);
  222. if (member->statusHasOnlineColor != isOnline) {
  223. member->statusHasOnlineColor = isOnline;
  224. member->statusText = QString();
  225. }
  226. if (member->statusHasOnlineColor) {
  227. ++newOnlineCount;
  228. if (!user->isSelf()) {
  229. onlyMe = false;
  230. }
  231. }
  232. }
  233. if (newOnlineCount == 1 && onlyMe) {
  234. newOnlineCount = 0;
  235. }
  236. if (_onlineCount != newOnlineCount) {
  237. _onlineCount = newOnlineCount;
  238. }
  239. }
  240. auto GroupMembersWidget::addUser(
  241. not_null<ChatData*> chat,
  242. not_null<UserData*> user)
  243. -> not_null<Member*> {
  244. const auto member = computeMember(user);
  245. setItemFlags(member, chat);
  246. addItem(member);
  247. return member;
  248. }
  249. void GroupMembersWidget::fillChatMembers(not_null<ChatData*> chat) {
  250. if (chat->participants.empty()) {
  251. return;
  252. }
  253. clearItems();
  254. if (!chat->amIn()) {
  255. return;
  256. }
  257. _sortByOnline = true;
  258. reserveItemsForSize(chat->participants.size());
  259. addUser(chat, chat->session().user())->onlineForSort
  260. = std::numeric_limits<TimeId>::max();
  261. for (const auto &user : chat->participants) {
  262. if (!user->isSelf()) {
  263. addUser(chat, user);
  264. }
  265. }
  266. }
  267. void GroupMembersWidget::setItemFlags(
  268. not_null<Item*> item,
  269. not_null<ChatData*> chat) {
  270. const auto user = getMember(item)->user();
  271. const auto isCreator = (peerFromUser(chat->creator) == item->peer->id);
  272. const auto isAdmin = (item->peer->isSelf() && chat->hasAdminRights())
  273. || chat->admins.contains(user);
  274. const auto rank = isCreator
  275. ? tr::lng_owner_badge(tr::now)
  276. : isAdmin
  277. ? tr::lng_admin_badge(tr::now)
  278. : std::optional<QString>();
  279. item->rank = rank;
  280. item->rankWidth = rank ? st::normalFont->width(*rank) : 0;
  281. if (item->peer->id == chat->session().userPeerId()) {
  282. item->hasRemoveLink = false;
  283. } else if (chat->amCreator()
  284. || ((chat->adminRights() & ChatAdminRight::BanUsers)
  285. && !rank.has_value())) {
  286. item->hasRemoveLink = true;
  287. } else if (chat->invitedByMe.contains(user) && !rank.has_value()) {
  288. item->hasRemoveLink = true;
  289. } else {
  290. item->hasRemoveLink = false;
  291. }
  292. }
  293. auto GroupMembersWidget::addUser(
  294. not_null<ChannelData*> megagroup,
  295. not_null<UserData*> user)
  296. -> not_null<Member*> {
  297. const auto member = computeMember(user);
  298. setItemFlags(member, megagroup);
  299. addItem(member);
  300. return member;
  301. }
  302. void GroupMembersWidget::fillMegagroupMembers(
  303. not_null<ChannelData*> megagroup) {
  304. Expects(megagroup->mgInfo != nullptr);
  305. if (megagroup->mgInfo->lastParticipants.empty()) {
  306. return;
  307. } else if (!megagroup->canViewMembers()) {
  308. clearItems();
  309. return;
  310. }
  311. _sortByOnline = (megagroup->membersCount() > 0)
  312. && (megagroup->membersCount()
  313. <= megagroup->session().serverConfig().chatSizeMax);
  314. auto &membersList = megagroup->mgInfo->lastParticipants;
  315. if (_sortByOnline) {
  316. clearItems();
  317. reserveItemsForSize(membersList.size());
  318. if (megagroup->amIn()) {
  319. addUser(megagroup, megagroup->session().user())->onlineForSort
  320. = std::numeric_limits<TimeId>::max();
  321. }
  322. } else if (membersList.size() >= itemsCount()) {
  323. if (addUsersToEnd(megagroup)) {
  324. return;
  325. }
  326. }
  327. if (!_sortByOnline) {
  328. clearItems();
  329. reserveItemsForSize(membersList.size());
  330. }
  331. for (const auto user : membersList) {
  332. if (!_sortByOnline || !user->isSelf()) {
  333. addUser(megagroup, user);
  334. }
  335. }
  336. }
  337. bool GroupMembersWidget::addUsersToEnd(not_null<ChannelData*> megagroup) {
  338. auto &membersList = megagroup->mgInfo->lastParticipants;
  339. auto &itemsList = items();
  340. for (int i = 0, count = itemsList.size(); i < count; ++i) {
  341. if (itemsList[i]->peer != membersList.at(i)) {
  342. return false;
  343. }
  344. }
  345. reserveItemsForSize(membersList.size());
  346. for (int i = itemsCount(), count = membersList.size(); i < count; ++i) {
  347. addUser(megagroup, membersList.at(i));
  348. }
  349. return true;
  350. }
  351. void GroupMembersWidget::setItemFlags(
  352. not_null<Item*> item,
  353. not_null<ChannelData*> megagroup) {
  354. const auto amCreator = item->peer->isSelf() && megagroup->amCreator();
  355. const auto amAdmin = item->peer->isSelf() && megagroup->hasAdminRights();
  356. const auto user = getMember(item)->user();
  357. const auto adminIt = megagroup->mgInfo->lastAdmins.find(user);
  358. const auto isAdmin = (adminIt != megagroup->mgInfo->lastAdmins.cend());
  359. const auto isCreator = (megagroup->mgInfo->creator == item->peer);
  360. const auto rankIt = megagroup->mgInfo->admins.find(peerToUser(user->id));
  361. const auto adminCanEdit = isAdmin && adminIt->second.canEdit;
  362. const auto rank = (amCreator || isCreator)
  363. ? (!megagroup->mgInfo->creatorRank.isEmpty()
  364. ? megagroup->mgInfo->creatorRank
  365. : tr::lng_owner_badge(tr::now))
  366. : (amAdmin || isAdmin)
  367. ? ((rankIt != megagroup->mgInfo->admins.end()
  368. && !rankIt->second.isEmpty())
  369. ? rankIt->second
  370. : tr::lng_admin_badge(tr::now))
  371. : std::optional<QString>();
  372. if (item->rank != rank) {
  373. item->rank = rank;
  374. item->rankWidth = rank ? st::normalFont->width(*rank) : 0;
  375. auto user = item->peer->asUser();
  376. Assert(user != nullptr);
  377. if (user->isBot()) {
  378. // Update "has access to messages" status.
  379. item->statusText = QString();
  380. updateItemStatusText(item);
  381. }
  382. }
  383. if (item->peer->isSelf()) {
  384. item->hasRemoveLink = false;
  385. } else if (megagroup->amCreator()
  386. || (megagroup->canBanMembers()
  387. && (!rank.has_value() || adminCanEdit))) {
  388. item->hasRemoveLink = true;
  389. } else {
  390. item->hasRemoveLink = false;
  391. }
  392. }
  393. auto GroupMembersWidget::computeMember(not_null<UserData*> user)
  394. -> not_null<Member*> {
  395. auto it = _membersByUser.find(user);
  396. if (it == _membersByUser.cend()) {
  397. auto member = new Member(user);
  398. it = _membersByUser.emplace(user, member).first;
  399. member->lastseen = user->lastseen();
  400. member->statusHasOnlineColor = !user->isBot()
  401. && member->lastseen.isOnline(_now);
  402. member->onlineForSort = Data::SortByOnlineValue(user, _now);
  403. }
  404. return it->second;
  405. }
  406. void GroupMembersWidget::updateOnlineDisplay() {
  407. if (_sortByOnline) {
  408. _now = base::unixtime::now();
  409. bool changed = false;
  410. for (const auto item : items()) {
  411. if (!item->statusHasOnlineColor) {
  412. if (!item->peer->isSelf()) {
  413. continue;
  414. } else {
  415. break;
  416. }
  417. }
  418. auto member = getMember(item);
  419. bool isOnline = !member->user()->isBot()
  420. && member->lastseen.isOnline(_now);
  421. if (!isOnline) {
  422. changed = true;
  423. }
  424. }
  425. if (changed) {
  426. updateOnlineCount();
  427. }
  428. }
  429. update();
  430. }
  431. GroupMembersWidget::~GroupMembersWidget() {
  432. auto members = base::take(_membersByUser);
  433. for (const auto &[_, member] : members) {
  434. delete member;
  435. }
  436. }
  437. } // namespace Profile