data_stories.cpp 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243
  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/data_stories.h"
  8. #include "base/unixtime.h"
  9. #include "apiwrap.h"
  10. #include "core/application.h"
  11. #include "data/data_changes.h"
  12. #include "data/data_channel.h"
  13. #include "data/data_document.h"
  14. #include "data/data_folder.h"
  15. #include "data/data_photo.h"
  16. #include "data/data_user.h"
  17. #include "data/data_session.h"
  18. #include "history/history.h"
  19. #include "history/history_item.h"
  20. #include "lang/lang_keys.h"
  21. #include "main/main_app_config.h"
  22. #include "main/main_session.h"
  23. #include "ui/layers/show.h"
  24. #include "ui/text/text_utilities.h"
  25. namespace Data {
  26. namespace {
  27. constexpr auto kMaxResolveTogether = 100;
  28. constexpr auto kIgnorePreloadAroundIfLoaded = 15;
  29. constexpr auto kPreloadAroundCount = 30;
  30. constexpr auto kMarkAsReadDelay = 3 * crl::time(1000);
  31. constexpr auto kIncrementViewsDelay = 5 * crl::time(1000);
  32. constexpr auto kArchiveFirstPerPage = 30;
  33. constexpr auto kArchivePerPage = 100;
  34. constexpr auto kSavedFirstPerPage = 30;
  35. constexpr auto kSavedPerPage = 100;
  36. constexpr auto kMaxPreloadSources = 10;
  37. constexpr auto kStillPreloadFromFirst = 3;
  38. constexpr auto kMaxSegmentsCount = 180;
  39. constexpr auto kPollingIntervalChat = 5 * TimeId(60);
  40. constexpr auto kPollingIntervalViewer = 1 * TimeId(60);
  41. constexpr auto kPollViewsInterval = 10 * crl::time(1000);
  42. constexpr auto kPollingViewsPerPage = Story::kRecentViewersMax;
  43. using UpdateFlag = StoryUpdate::Flag;
  44. [[nodiscard]] std::optional<StoryMedia> ParseMedia(
  45. not_null<Session*> owner,
  46. const MTPMessageMedia &media) {
  47. return media.match([&](const MTPDmessageMediaPhoto &data)
  48. -> std::optional<StoryMedia> {
  49. if (const auto photo = data.vphoto()) {
  50. const auto result = owner->processPhoto(*photo);
  51. if (!result->isNull()) {
  52. return StoryMedia{ result };
  53. }
  54. }
  55. return {};
  56. }, [&](const MTPDmessageMediaDocument &data)
  57. -> std::optional<StoryMedia> {
  58. if (const auto document = data.vdocument()) {
  59. const auto result = owner->processDocument(
  60. *document,
  61. data.valt_documents());
  62. if (!result->isNull()
  63. && (result->isGifv() || result->isVideoFile())) {
  64. result->setStoryMedia(true);
  65. return StoryMedia{ result };
  66. }
  67. }
  68. return {};
  69. }, [&](const MTPDmessageMediaUnsupported &data) {
  70. return std::make_optional(StoryMedia{ v::null });
  71. }, [](const auto &) { return std::optional<StoryMedia>(); });
  72. }
  73. } // namespace
  74. std::vector<StoryId> RespectingPinned(const StoriesIds &ids) {
  75. if (ids.pinnedToTop.empty()) {
  76. return ids.list | ranges::to_vector;
  77. }
  78. auto result = std::vector<StoryId>();
  79. result.reserve(ids.list.size());
  80. result.insert(end(result), begin(ids.pinnedToTop), end(ids.pinnedToTop));
  81. for (const auto &id : ids.list) {
  82. if (!ranges::contains(ids.pinnedToTop, id)) {
  83. result.push_back(id);
  84. }
  85. }
  86. return result;
  87. }
  88. StoriesSourceInfo StoriesSource::info() const {
  89. return {
  90. .id = peer->id,
  91. .last = ids.empty() ? 0 : ids.back().date,
  92. .count = uint32(std::min(int(ids.size()), kMaxSegmentsCount)),
  93. .unreadCount = uint32(std::min(unreadCount(), kMaxSegmentsCount)),
  94. .premium = (peer->isUser() && peer->asUser()->isPremium()) ? 1U : 0,
  95. };
  96. }
  97. int StoriesSource::unreadCount() const {
  98. const auto i = ids.lower_bound(StoryIdDates{ .id = readTill + 1 });
  99. return int(end(ids) - i);
  100. }
  101. StoryIdDates StoriesSource::toOpen() const {
  102. if (ids.empty()) {
  103. return {};
  104. }
  105. const auto i = ids.lower_bound(StoryIdDates{ readTill + 1 });
  106. return (i != end(ids)) ? *i : ids.front();
  107. }
  108. Stories::Stories(not_null<Session*> owner)
  109. : _owner(owner)
  110. , _expireTimer([=] { processExpired(); })
  111. , _markReadTimer([=] { sendMarkAsReadRequests(); })
  112. , _incrementViewsTimer([=] { sendIncrementViewsRequests(); })
  113. , _pollingTimer([=] { sendPollingRequests(); })
  114. , _pollingViewsTimer([=] { sendPollingViewsRequests(); }) {
  115. crl::on_main(this, [=] {
  116. session().changes().peerUpdates(
  117. Data::PeerUpdate::Flag::Rights
  118. ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
  119. const auto channel = update.peer->asChannel();
  120. if (!channel) {
  121. return;
  122. } else if (!channel->canEditStories()) {
  123. const auto peerId = channel->id;
  124. const auto i = _peersWithDeletedStories.find(peerId);
  125. if (i != end(_peersWithDeletedStories)) {
  126. _peersWithDeletedStories.erase(i);
  127. for (auto j = begin(_deleted); j != end(_deleted);) {
  128. if (j->peer == peerId) {
  129. j = _deleted.erase(j);
  130. } else {
  131. ++j;
  132. }
  133. }
  134. }
  135. } else {
  136. clearArchive(channel);
  137. }
  138. }, _lifetime);
  139. });
  140. }
  141. Stories::~Stories() {
  142. Expects(_pollingSettings.empty());
  143. Expects(_pollingViews.empty());
  144. }
  145. Session &Stories::owner() const {
  146. return *_owner;
  147. }
  148. Main::Session &Stories::session() const {
  149. return _owner->session();
  150. }
  151. void Stories::apply(const MTPDupdateStory &data) {
  152. const auto peerId = peerFromMTP(data.vpeer());
  153. const auto peer = _owner->peer(peerId);
  154. const auto now = base::unixtime::now();
  155. const auto idDates = parseAndApply(peer, data.vstory(), now);
  156. if (!idDates) {
  157. return;
  158. }
  159. const auto expired = (idDates.expires <= now);
  160. if (expired) {
  161. applyExpired({ peerId, idDates.id });
  162. return;
  163. }
  164. const auto i = _all.find(peerId);
  165. if (i == end(_all)) {
  166. requestPeerStories(peer);
  167. return;
  168. } else if (i->second.ids.contains(idDates)) {
  169. return;
  170. }
  171. const auto wasInfo = i->second.info();
  172. i->second.ids.emplace(idDates);
  173. const auto nowInfo = i->second.info();
  174. if (peer->isSelf() && i->second.readTill < idDates.id) {
  175. _readTill[peerId] = i->second.readTill = idDates.id;
  176. }
  177. if (wasInfo == nowInfo) {
  178. return;
  179. }
  180. const auto refreshInList = [&](StorySourcesList list) {
  181. auto &sources = _sources[static_cast<int>(list)];
  182. const auto i = ranges::find(
  183. sources,
  184. peerId,
  185. &StoriesSourceInfo::id);
  186. if (i != end(sources)) {
  187. *i = nowInfo;
  188. sort(list);
  189. }
  190. };
  191. if (peer->hasStoriesHidden()) {
  192. refreshInList(StorySourcesList::Hidden);
  193. } else {
  194. refreshInList(StorySourcesList::NotHidden);
  195. }
  196. _sourceChanged.fire_copy(peerId);
  197. updatePeerStoriesState(peer);
  198. }
  199. void Stories::apply(const MTPDupdateReadStories &data) {
  200. bumpReadTill(peerFromMTP(data.vpeer()), data.vmax_id().v);
  201. }
  202. void Stories::apply(const MTPStoriesStealthMode &stealthMode) {
  203. const auto &data = stealthMode.data();
  204. _stealthMode = StealthMode{
  205. .enabledTill = data.vactive_until_date().value_or_empty(),
  206. .cooldownTill = data.vcooldown_until_date().value_or_empty(),
  207. };
  208. }
  209. void Stories::apply(not_null<PeerData*> peer, const MTPPeerStories *data) {
  210. if (!data) {
  211. applyDeletedFromSources(peer->id, StorySourcesList::NotHidden);
  212. applyDeletedFromSources(peer->id, StorySourcesList::Hidden);
  213. _all.erase(peer->id);
  214. _sourceChanged.fire_copy(peer->id);
  215. updatePeerStoriesState(peer);
  216. } else {
  217. parseAndApply(*data);
  218. }
  219. }
  220. Story *Stories::applySingle(PeerId peerId, const MTPstoryItem &story) {
  221. const auto idDates = parseAndApply(
  222. _owner->peer(peerId),
  223. story,
  224. base::unixtime::now());
  225. const auto value = idDates
  226. ? lookup({ peerId, idDates.id })
  227. : base::make_unexpected(NoStory::Deleted);
  228. return value ? value->get() : nullptr;
  229. }
  230. void Stories::requestPeerStories(
  231. not_null<PeerData*> peer,
  232. Fn<void()> done) {
  233. const auto &[i, ok] = _requestingPeerStories.emplace(peer);
  234. if (done) {
  235. i->second.push_back(std::move(done));
  236. }
  237. if (!ok) {
  238. return;
  239. }
  240. const auto finish = [=] {
  241. if (const auto callbacks = _requestingPeerStories.take(peer)) {
  242. for (const auto &callback : *callbacks) {
  243. callback();
  244. }
  245. }
  246. };
  247. _owner->session().api().request(MTPstories_GetPeerStories(
  248. peer->input
  249. )).done([=](const MTPstories_PeerStories &result) {
  250. const auto &data = result.data();
  251. _owner->processUsers(data.vusers());
  252. _owner->processChats(data.vchats());
  253. parseAndApply(data.vstories());
  254. finish();
  255. }).fail([=] {
  256. applyDeletedFromSources(peer->id, StorySourcesList::NotHidden);
  257. applyDeletedFromSources(peer->id, StorySourcesList::Hidden);
  258. finish();
  259. }).send();
  260. }
  261. void Stories::registerExpiring(TimeId expires, FullStoryId id) {
  262. for (auto i = _expiring.findFirst(expires)
  263. ; (i != end(_expiring)) && (i->first == expires)
  264. ; ++i) {
  265. if (i->second == id) {
  266. return;
  267. }
  268. }
  269. const auto reschedule = _expiring.empty()
  270. || (_expiring.front().first > expires);
  271. _expiring.emplace(expires, id);
  272. if (reschedule) {
  273. scheduleExpireTimer();
  274. }
  275. }
  276. void Stories::scheduleExpireTimer() {
  277. if (_expireSchedulePosted) {
  278. return;
  279. }
  280. _expireSchedulePosted = true;
  281. crl::on_main(this, [=] {
  282. if (!_expireSchedulePosted) {
  283. return;
  284. }
  285. _expireSchedulePosted = false;
  286. if (_expiring.empty()) {
  287. _expireTimer.cancel();
  288. } else {
  289. const auto nearest = _expiring.front().first;
  290. const auto now = base::unixtime::now();
  291. const auto delay = (nearest > now)
  292. ? std::min(nearest - now, 86'400)
  293. : 0;
  294. _expireTimer.callOnce(delay * crl::time(1000));
  295. }
  296. });
  297. }
  298. void Stories::processExpired() {
  299. const auto now = base::unixtime::now();
  300. auto expired = base::flat_set<FullStoryId>();
  301. auto i = begin(_expiring);
  302. for (; i != end(_expiring) && i->first <= now; ++i) {
  303. expired.emplace(i->second);
  304. }
  305. _expiring.erase(begin(_expiring), i);
  306. for (const auto &id : expired) {
  307. applyExpired(id);
  308. }
  309. if (!_expiring.empty()) {
  310. scheduleExpireTimer();
  311. }
  312. }
  313. Stories::Set *Stories::lookupArchive(not_null<PeerData*> peer) {
  314. const auto peerId = peer->id;
  315. if (hasArchive(peer)) {
  316. const auto i = _archive.find(peerId);
  317. return (i != end(_archive))
  318. ? &i->second
  319. : &_archive.emplace(peerId, Set()).first->second;
  320. }
  321. clearArchive(peer);
  322. return nullptr;
  323. }
  324. void Stories::clearArchive(not_null<PeerData*> peer) {
  325. const auto peerId = peer->id;
  326. const auto i = _archive.find(peerId);
  327. if (i == end(_archive)) {
  328. return;
  329. }
  330. auto archive = base::take(i->second);
  331. _archive.erase(i);
  332. for (const auto &id : archive.ids.list) {
  333. if (const auto story = lookup({ peerId, id })) {
  334. if ((*story)->expired() && !(*story)->inProfile()) {
  335. applyDeleted(peer, id);
  336. }
  337. }
  338. }
  339. _archiveChanged.fire_copy(peerId);
  340. }
  341. void Stories::parseAndApply(const MTPPeerStories &stories) {
  342. const auto &data = stories.data();
  343. const auto peerId = peerFromMTP(data.vpeer());
  344. const auto already = _readTill.find(peerId);
  345. const auto readTill = std::max(
  346. data.vmax_read_id().value_or_empty(),
  347. (already != end(_readTill) ? already->second : 0));
  348. const auto peer = _owner->peer(peerId);
  349. auto result = StoriesSource{
  350. .peer = peer,
  351. .readTill = readTill,
  352. .hidden = peer->hasStoriesHidden(),
  353. };
  354. const auto &list = data.vstories().v;
  355. const auto now = base::unixtime::now();
  356. result.ids.reserve(list.size());
  357. for (const auto &story : list) {
  358. if (const auto id = parseAndApply(result.peer, story, now)) {
  359. result.ids.emplace(id);
  360. }
  361. }
  362. if (result.ids.empty()) {
  363. applyDeletedFromSources(peerId, StorySourcesList::NotHidden);
  364. applyDeletedFromSources(peerId, StorySourcesList::Hidden);
  365. peer->setStoriesState(PeerData::StoriesState::None);
  366. return;
  367. } else if (peer->isSelf()) {
  368. result.readTill = result.ids.back().id;
  369. }
  370. _readTill[peerId] = result.readTill;
  371. const auto info = result.info();
  372. const auto i = _all.find(peerId);
  373. if (i != end(_all)) {
  374. if (i->second != result) {
  375. i->second = std::move(result);
  376. }
  377. } else {
  378. _all.emplace(peerId, std::move(result));
  379. }
  380. const auto add = [&](StorySourcesList list) {
  381. auto &sources = _sources[static_cast<int>(list)];
  382. const auto i = ranges::find(
  383. sources,
  384. peerId,
  385. &StoriesSourceInfo::id);
  386. if (i == end(sources)) {
  387. sources.push_back(info);
  388. } else if (*i == info) {
  389. return;
  390. } else {
  391. *i = info;
  392. }
  393. sort(list);
  394. };
  395. if (result.peer->isSelf()
  396. || (result.peer->isChannel() && result.peer->asChannel()->amIn())
  397. || (result.peer->isUser()
  398. && (result.peer->asUser()->isBot()
  399. || result.peer->asUser()->isContact()))
  400. || result.peer->isServiceUser()) {
  401. const auto hidden = result.peer->hasStoriesHidden();
  402. using List = StorySourcesList;
  403. add(hidden ? List::Hidden : List::NotHidden);
  404. applyDeletedFromSources(
  405. peerId,
  406. hidden ? List::NotHidden : List::Hidden);
  407. } else {
  408. applyDeletedFromSources(peerId, StorySourcesList::NotHidden);
  409. applyDeletedFromSources(peerId, StorySourcesList::Hidden);
  410. }
  411. _sourceChanged.fire_copy(peerId);
  412. updatePeerStoriesState(result.peer);
  413. }
  414. Story *Stories::parseAndApply(
  415. not_null<PeerData*> peer,
  416. const MTPDstoryItem &data,
  417. TimeId now) {
  418. const auto media = ParseMedia(_owner, data.vmedia());
  419. if (!media) {
  420. return nullptr;
  421. }
  422. const auto expires = data.vexpire_date().v;
  423. const auto expired = (expires <= now);
  424. if (expired && !data.is_pinned() && !hasArchive(peer)) {
  425. return nullptr;
  426. }
  427. const auto id = data.vid().v;
  428. const auto fullId = FullStoryId{ peer->id, id };
  429. auto &stories = _stories[peer->id];
  430. const auto i = stories.find(id);
  431. if (i != end(stories)) {
  432. const auto result = i->second.get();
  433. const auto mediaChanged = (result->media() != *media);
  434. result->applyChanges(*media, data, now);
  435. const auto j = _pollingSettings.find(result);
  436. if (j != end(_pollingSettings)) {
  437. maybeSchedulePolling(result, j->second, now);
  438. }
  439. if (mediaChanged) {
  440. _preloaded.remove(fullId);
  441. if (_preloading && _preloading->id() == fullId) {
  442. _preloading = nullptr;
  443. rebuildPreloadSources(StorySourcesList::NotHidden);
  444. rebuildPreloadSources(StorySourcesList::Hidden);
  445. continuePreloading();
  446. }
  447. _owner->refreshStoryItemViews(fullId);
  448. }
  449. return result;
  450. }
  451. const auto wasDeleted = _deleted.remove(fullId);
  452. const auto result = stories.emplace(id, std::make_unique<Story>(
  453. id,
  454. peer,
  455. StoryMedia{ *media },
  456. data,
  457. now
  458. )).first->second.get();
  459. if (const auto archive = lookupArchive(peer)) {
  460. const auto added = archive->ids.list.emplace(id).second;
  461. if (added) {
  462. if (archive->total >= 0 && id > archive->lastId) {
  463. ++archive->total;
  464. }
  465. _archiveChanged.fire_copy(peer->id);
  466. }
  467. }
  468. if (expired) {
  469. _expiring.remove(expires, fullId);
  470. applyExpired(fullId);
  471. } else {
  472. registerExpiring(expires, fullId);
  473. }
  474. if (wasDeleted) {
  475. _owner->refreshStoryItemViews(fullId);
  476. }
  477. return result;
  478. }
  479. StoryIdDates Stories::parseAndApply(
  480. not_null<PeerData*> peer,
  481. const MTPstoryItem &story,
  482. TimeId now) {
  483. return story.match([&](const MTPDstoryItem &data) {
  484. if (const auto story = parseAndApply(peer, data, now)) {
  485. return story->idDates();
  486. }
  487. applyDeleted(peer, data.vid().v);
  488. return StoryIdDates();
  489. }, [&](const MTPDstoryItemSkipped &data) {
  490. const auto expires = data.vexpire_date().v;
  491. const auto expired = (expires <= now);
  492. const auto fullId = FullStoryId{ peer->id, data.vid().v };
  493. if (!expired) {
  494. registerExpiring(expires, fullId);
  495. } else if (!hasArchive(peer)) {
  496. applyDeleted(peer, data.vid().v);
  497. return StoryIdDates();
  498. } else {
  499. _expiring.remove(expires, fullId);
  500. applyExpired(fullId);
  501. }
  502. return StoryIdDates{
  503. data.vid().v,
  504. data.vdate().v,
  505. data.vexpire_date().v,
  506. };
  507. }, [&](const MTPDstoryItemDeleted &data) {
  508. applyDeleted(peer, data.vid().v);
  509. return StoryIdDates();
  510. });
  511. }
  512. void Stories::updateDependentMessages(not_null<Data::Story*> story) {
  513. const auto i = _dependentMessages.find(story);
  514. if (i != end(_dependentMessages)) {
  515. for (const auto &dependent : i->second) {
  516. dependent->updateDependencyItem();
  517. }
  518. }
  519. session().changes().storyUpdated(
  520. story,
  521. Data::StoryUpdate::Flag::Edited);
  522. }
  523. void Stories::registerDependentMessage(
  524. not_null<HistoryItem*> dependent,
  525. not_null<Data::Story*> dependency) {
  526. _dependentMessages[dependency].emplace(dependent);
  527. }
  528. void Stories::unregisterDependentMessage(
  529. not_null<HistoryItem*> dependent,
  530. not_null<Data::Story*> dependency) {
  531. const auto i = _dependentMessages.find(dependency);
  532. if (i != end(_dependentMessages)) {
  533. if (i->second.remove(dependent) && i->second.empty()) {
  534. _dependentMessages.erase(i);
  535. }
  536. }
  537. }
  538. void Stories::savedStateChanged(not_null<Story*> story) {
  539. const auto id = story->id();
  540. const auto peer = story->peer()->id;
  541. const auto inProfile = story->inProfile();
  542. if (inProfile) {
  543. auto &saved = _saved[peer];
  544. const auto added = saved.ids.list.emplace(id).second;
  545. if (added) {
  546. if (saved.total >= 0 && id > saved.lastId) {
  547. ++saved.total;
  548. }
  549. _savedChanged.fire_copy(peer);
  550. }
  551. } else if (const auto i = _saved.find(peer); i != end(_saved)) {
  552. auto &saved = i->second;
  553. if (saved.ids.list.remove(id)) {
  554. if (saved.total > 0) {
  555. --saved.total;
  556. }
  557. _savedChanged.fire_copy(peer);
  558. }
  559. }
  560. }
  561. void Stories::loadMore(StorySourcesList list) {
  562. const auto index = static_cast<int>(list);
  563. if (_loadMoreRequestId[index] || _sourcesLoaded[index]) {
  564. return;
  565. }
  566. const auto hidden = (list == StorySourcesList::Hidden);
  567. const auto api = &_owner->session().api();
  568. using Flag = MTPstories_GetAllStories::Flag;
  569. _loadMoreRequestId[index] = api->request(MTPstories_GetAllStories(
  570. MTP_flags((hidden ? Flag::f_hidden : Flag())
  571. | (_sourcesStates[index].isEmpty()
  572. ? Flag(0)
  573. : (Flag::f_next | Flag::f_state))),
  574. MTP_string(_sourcesStates[index])
  575. )).done([=](const MTPstories_AllStories &result) {
  576. _loadMoreRequestId[index] = 0;
  577. result.match([&](const MTPDstories_allStories &data) {
  578. _owner->processUsers(data.vusers());
  579. _owner->processChats(data.vchats());
  580. _sourcesStates[index] = qs(data.vstate());
  581. _sourcesLoaded[index] = !data.is_has_more();
  582. for (const auto &single : data.vpeer_stories().v) {
  583. parseAndApply(single);
  584. }
  585. }, [](const MTPDstories_allStoriesNotModified &) {
  586. });
  587. result.match([&](const auto &data) {
  588. apply(data.vstealth_mode());
  589. });
  590. preloadListsMore();
  591. }).fail([=] {
  592. _loadMoreRequestId[index] = 0;
  593. }).send();
  594. }
  595. void Stories::preloadListsMore() {
  596. if (_loadMoreRequestId[static_cast<int>(StorySourcesList::NotHidden)]
  597. || _loadMoreRequestId[static_cast<int>(StorySourcesList::Hidden)]) {
  598. return;
  599. }
  600. const auto loading = [&](StorySourcesList list) {
  601. return _loadMoreRequestId[static_cast<int>(list)] != 0;
  602. };
  603. const auto countLoaded = [&](StorySourcesList list) {
  604. const auto index = static_cast<int>(list);
  605. return _sourcesLoaded[index] || !_sourcesStates[index].isEmpty();
  606. };
  607. if (loading(StorySourcesList::NotHidden)
  608. || loading(StorySourcesList::Hidden)) {
  609. return;
  610. } else if (!countLoaded(StorySourcesList::NotHidden)) {
  611. loadMore(StorySourcesList::NotHidden);
  612. } else if (!countLoaded(StorySourcesList::Hidden)) {
  613. loadMore(StorySourcesList::Hidden);
  614. } else if (!archiveCountKnown(_owner->session().userPeerId())) {
  615. archiveLoadMore(_owner->session().userPeerId());
  616. }
  617. }
  618. void Stories::notifySourcesChanged(StorySourcesList list) {
  619. _sourcesChanged[static_cast<int>(list)].fire({});
  620. if (list == StorySourcesList::Hidden) {
  621. pushHiddenCountsToFolder();
  622. }
  623. }
  624. void Stories::pushHiddenCountsToFolder() {
  625. const auto &list = sources(StorySourcesList::Hidden);
  626. if (list.empty()) {
  627. if (_folderForHidden) {
  628. _folderForHidden->updateStoriesCount(0, 0);
  629. }
  630. return;
  631. }
  632. if (!_folderForHidden) {
  633. _folderForHidden = _owner->folder(Folder::kId);
  634. }
  635. const auto count = int(list.size());
  636. const auto unread = ranges::count_if(
  637. list,
  638. [](const StoriesSourceInfo &info) { return info.unreadCount > 0; });
  639. _folderForHidden->updateStoriesCount(count, unread);
  640. }
  641. void Stories::sendResolveRequests() {
  642. if (!_resolveSent.empty()) {
  643. return;
  644. }
  645. auto leftToSend = kMaxResolveTogether;
  646. auto byPeer = base::flat_map<PeerId, QVector<MTPint>>();
  647. for (auto i = begin(_resolvePending); i != end(_resolvePending);) {
  648. const auto peerId = i->first;
  649. auto &ids = i->second;
  650. auto &sent = _resolveSent[peerId];
  651. if (ids.size() <= leftToSend) {
  652. sent = base::take(ids);
  653. i = _resolvePending.erase(i); // Invalidates `ids`.
  654. leftToSend -= int(sent.size());
  655. } else {
  656. sent = {
  657. std::make_move_iterator(begin(ids)),
  658. std::make_move_iterator(begin(ids) + leftToSend)
  659. };
  660. ids.erase(begin(ids), begin(ids) + leftToSend);
  661. leftToSend = 0;
  662. }
  663. auto &prepared = byPeer[peerId];
  664. for (auto &[storyId, callbacks] : sent) {
  665. prepared.push_back(MTP_int(storyId));
  666. }
  667. if (!leftToSend) {
  668. break;
  669. }
  670. }
  671. const auto api = &_owner->session().api();
  672. for (auto &entry : byPeer) {
  673. const auto peerId = entry.first;
  674. auto &prepared = entry.second;
  675. const auto finish = [=](PeerId peerId) {
  676. const auto sent = _resolveSent.take(peerId);
  677. Assert(sent.has_value());
  678. for (const auto &[storyId, list] : *sent) {
  679. finalizeResolve({ peerId, storyId });
  680. for (const auto &callback : list) {
  681. callback();
  682. }
  683. }
  684. _itemsChanged.fire_copy(peerId);
  685. if (_resolveSent.empty() && !_resolvePending.empty()) {
  686. crl::on_main(&session(), [=] { sendResolveRequests(); });
  687. }
  688. };
  689. const auto peer = _owner->session().data().peer(peerId);
  690. api->request(MTPstories_GetStoriesByID(
  691. peer->input,
  692. MTP_vector<MTPint>(prepared)
  693. )).done([=](const MTPstories_Stories &result) {
  694. owner().processUsers(result.data().vusers());
  695. owner().processChats(result.data().vchats());
  696. processResolvedStories(peer, result.data().vstories().v);
  697. finish(peer->id);
  698. }).fail([=] {
  699. finish(peerId);
  700. }).send();
  701. }
  702. }
  703. void Stories::processResolvedStories(
  704. not_null<PeerData*> peer,
  705. const QVector<MTPStoryItem> &list) {
  706. const auto now = base::unixtime::now();
  707. for (const auto &item : list) {
  708. item.match([&](const MTPDstoryItem &data) {
  709. if (!parseAndApply(peer, data, now)) {
  710. applyDeleted(peer, data.vid().v);
  711. }
  712. }, [&](const MTPDstoryItemSkipped &data) {
  713. LOG(("API Error: Unexpected storyItemSkipped in resolve."));
  714. }, [&](const MTPDstoryItemDeleted &data) {
  715. applyDeleted(peer, data.vid().v);
  716. });
  717. }
  718. }
  719. void Stories::finalizeResolve(FullStoryId id) {
  720. const auto already = lookup(id);
  721. if (!already.has_value() && already.error() == NoStory::Unknown) {
  722. LOG(("API Error: Could not resolve story %1_%2"
  723. ).arg(id.peer.value
  724. ).arg(id.story));
  725. applyDeleted(_owner->peer(id.peer), id.story);
  726. }
  727. }
  728. void Stories::applyDeleted(not_null<PeerData*> peer, StoryId id) {
  729. const auto fullId = FullStoryId{ peer->id, id };
  730. applyRemovedFromActive(fullId);
  731. if (const auto channel = peer->asChannel()) {
  732. if (!hasArchive(channel)) {
  733. _peersWithDeletedStories.emplace(channel->id);
  734. }
  735. }
  736. _deleted.emplace(fullId);
  737. const auto peerId = peer->id;
  738. const auto i = _stories.find(peerId);
  739. if (i != end(_stories)) {
  740. const auto j = i->second.find(id);
  741. if (j != end(i->second)) {
  742. const auto &story
  743. = _deletingStories[fullId]
  744. = std::move(j->second);
  745. _expiring.remove(story->expires(), story->fullId());
  746. i->second.erase(j);
  747. session().changes().storyUpdated(
  748. story.get(),
  749. UpdateFlag::Destroyed);
  750. removeDependencyStory(story.get());
  751. if (hasArchive(story->peer())) {
  752. if (const auto k = _archive.find(peerId)
  753. ; k != end(_archive)) {
  754. const auto archive = &k->second;
  755. if (archive->ids.list.remove(id)) {
  756. if (archive->total > 0) {
  757. --archive->total;
  758. }
  759. _archiveChanged.fire_copy(peerId);
  760. }
  761. }
  762. }
  763. if (story->inProfile()) {
  764. if (const auto k = _saved.find(peerId); k != end(_saved)) {
  765. const auto saved = &k->second;
  766. if (saved->ids.list.remove(id)) {
  767. if (saved->total > 0) {
  768. --saved->total;
  769. }
  770. _savedChanged.fire_copy(peerId);
  771. }
  772. }
  773. }
  774. if (_preloading && _preloading->id() == fullId) {
  775. _preloading = nullptr;
  776. preloadFinished(fullId);
  777. }
  778. _owner->refreshStoryItemViews(fullId);
  779. Assert(!_pollingSettings.contains(story.get()));
  780. if (const auto j = _items.find(peerId); j != end(_items)) {
  781. const auto k = j->second.find(id);
  782. if (k != end(j->second)) {
  783. Assert(!k->second.lock());
  784. j->second.erase(k);
  785. if (j->second.empty()) {
  786. _items.erase(j);
  787. }
  788. }
  789. }
  790. if (i->second.empty()) {
  791. _stories.erase(i);
  792. }
  793. _deletingStories.remove(fullId);
  794. }
  795. }
  796. }
  797. void Stories::applyExpired(FullStoryId id) {
  798. if (const auto maybeStory = lookup(id)) {
  799. const auto story = *maybeStory;
  800. if (!hasArchive(story->peer()) && !story->inProfile()) {
  801. applyDeleted(story->peer(), id.story);
  802. return;
  803. }
  804. }
  805. applyRemovedFromActive(id);
  806. }
  807. void Stories::applyRemovedFromActive(FullStoryId id) {
  808. const auto removeFromList = [&](StorySourcesList list) {
  809. const auto index = static_cast<int>(list);
  810. auto &sources = _sources[index];
  811. const auto i = ranges::find(
  812. sources,
  813. id.peer,
  814. &StoriesSourceInfo::id);
  815. if (i != end(sources)) {
  816. sources.erase(i);
  817. notifySourcesChanged(list);
  818. }
  819. };
  820. const auto i = _all.find(id.peer);
  821. if (i != end(_all)) {
  822. const auto j = i->second.ids.lower_bound(StoryIdDates{ id.story });
  823. if (j != end(i->second.ids) && j->id == id.story) {
  824. i->second.ids.erase(j);
  825. const auto peer = i->second.peer;
  826. if (i->second.ids.empty()) {
  827. _all.erase(i);
  828. removeFromList(StorySourcesList::NotHidden);
  829. removeFromList(StorySourcesList::Hidden);
  830. }
  831. _sourceChanged.fire_copy(id.peer);
  832. updatePeerStoriesState(peer);
  833. }
  834. }
  835. }
  836. void Stories::applyDeletedFromSources(PeerId id, StorySourcesList list) {
  837. auto &sources = _sources[static_cast<int>(list)];
  838. const auto i = ranges::find(
  839. sources,
  840. id,
  841. &StoriesSourceInfo::id);
  842. if (i != end(sources)) {
  843. sources.erase(i);
  844. }
  845. notifySourcesChanged(list);
  846. }
  847. void Stories::removeDependencyStory(not_null<Story*> story) {
  848. const auto i = _dependentMessages.find(story);
  849. if (i != end(_dependentMessages)) {
  850. const auto items = std::move(i->second);
  851. _dependentMessages.erase(i);
  852. for (const auto &dependent : items) {
  853. dependent->dependencyStoryRemoved(story);
  854. }
  855. }
  856. }
  857. void Stories::sort(StorySourcesList list) {
  858. const auto index = static_cast<int>(list);
  859. auto &sources = _sources[index];
  860. const auto self = _owner->session().userPeerId();
  861. const auto changelogSenderId = UserData::kServiceNotificationsId;
  862. const auto proj = [&](const StoriesSourceInfo &info) {
  863. const auto key = int64(info.last)
  864. + (info.premium ? (int64(1) << 47) : 0)
  865. + ((info.id == changelogSenderId) ? (int64(1) << 47) : 0)
  866. + ((info.unreadCount > 0) ? (int64(1) << 49) : 0)
  867. + ((info.id == self) ? (int64(1) << 50) : 0);
  868. return std::make_pair(key, info.id);
  869. };
  870. ranges::sort(sources, ranges::greater(), proj);
  871. notifySourcesChanged(list);
  872. preloadSourcesChanged(list);
  873. }
  874. std::shared_ptr<HistoryItem> Stories::lookupItem(not_null<Story*> story) {
  875. const auto i = _items.find(story->peer()->id);
  876. if (i == end(_items)) {
  877. return nullptr;
  878. }
  879. const auto j = i->second.find(story->id());
  880. if (j == end(i->second)) {
  881. return nullptr;
  882. }
  883. return j->second.lock();
  884. }
  885. StealthMode Stories::stealthMode() const {
  886. return _stealthMode.current();
  887. }
  888. rpl::producer<StealthMode> Stories::stealthModeValue() const {
  889. return _stealthMode.value();
  890. }
  891. void Stories::activateStealthMode(Fn<void()> done) {
  892. const auto api = &session().api();
  893. using Flag = MTPstories_ActivateStealthMode::Flag;
  894. api->request(MTPstories_ActivateStealthMode(
  895. MTP_flags(Flag::f_past | Flag::f_future)
  896. )).done([=](const MTPUpdates &result) {
  897. api->applyUpdates(result);
  898. if (done) done();
  899. }).fail([=] {
  900. if (done) done();
  901. }).send();
  902. }
  903. void Stories::sendReaction(FullStoryId id, Data::ReactionId reaction) {
  904. if (const auto maybeStory = lookup(id)) {
  905. const auto story = *maybeStory;
  906. story->setReactionId(reaction);
  907. const auto api = &session().api();
  908. api->request(MTPstories_SendReaction(
  909. MTP_flags(0),
  910. story->peer()->input,
  911. MTP_int(id.story),
  912. ReactionToMTP(reaction)
  913. )).send();
  914. }
  915. }
  916. std::shared_ptr<HistoryItem> Stories::resolveItem(not_null<Story*> story) {
  917. auto &items = _items[story->peer()->id];
  918. auto i = items.find(story->id());
  919. if (i == end(items)) {
  920. i = items.emplace(story->id()).first;
  921. } else if (const auto result = i->second.lock()) {
  922. return result;
  923. }
  924. const auto history = _owner->history(story->peer());
  925. auto result = std::shared_ptr<HistoryItem>(
  926. history->makeMessage(StoryIdToMsgId(story->id()), story).get(),
  927. HistoryItem::Destroyer());
  928. i->second = result;
  929. return result;
  930. }
  931. std::shared_ptr<HistoryItem> Stories::resolveItem(FullStoryId id) {
  932. const auto story = lookup(id);
  933. return story ? resolveItem(*story) : std::shared_ptr<HistoryItem>();
  934. }
  935. const StoriesSource *Stories::source(PeerId id) const {
  936. const auto i = _all.find(id);
  937. return (i != end(_all)) ? &i->second : nullptr;
  938. }
  939. const std::vector<StoriesSourceInfo> &Stories::sources(
  940. StorySourcesList list) const {
  941. return _sources[static_cast<int>(list)];
  942. }
  943. bool Stories::sourcesLoaded(StorySourcesList list) const {
  944. return _sourcesLoaded[static_cast<int>(list)];
  945. }
  946. rpl::producer<> Stories::sourcesChanged(StorySourcesList list) const {
  947. return _sourcesChanged[static_cast<int>(list)].events();
  948. }
  949. rpl::producer<PeerId> Stories::sourceChanged() const {
  950. return _sourceChanged.events();
  951. }
  952. rpl::producer<PeerId> Stories::itemsChanged() const {
  953. return _itemsChanged.events();
  954. }
  955. base::expected<not_null<Story*>, NoStory> Stories::lookup(
  956. FullStoryId id) const {
  957. const auto i = _stories.find(id.peer);
  958. if (i != end(_stories)) {
  959. const auto j = i->second.find(id.story);
  960. if (j != end(i->second)) {
  961. return j->second.get();
  962. }
  963. }
  964. return base::make_unexpected(
  965. _deleted.contains(id) ? NoStory::Deleted : NoStory::Unknown);
  966. }
  967. void Stories::resolve(FullStoryId id, Fn<void()> done, bool force) {
  968. if (!force) {
  969. const auto already = lookup(id);
  970. if (already.has_value() || already.error() != NoStory::Unknown) {
  971. if (done) {
  972. done();
  973. }
  974. return;
  975. }
  976. }
  977. if (const auto i = _resolveSent.find(id.peer); i != end(_resolveSent)) {
  978. if (const auto j = i->second.find(id.story); j != end(i->second)) {
  979. if (done) {
  980. j->second.push_back(std::move(done));
  981. }
  982. return;
  983. }
  984. }
  985. auto &ids = _resolvePending[id.peer];
  986. if (ids.empty()) {
  987. crl::on_main(&session(), [=] {
  988. sendResolveRequests();
  989. });
  990. }
  991. auto &callbacks = ids[id.story];
  992. if (done) {
  993. callbacks.push_back(std::move(done));
  994. }
  995. }
  996. void Stories::loadAround(FullStoryId id, StoriesContext context) {
  997. if (v::is<StoriesContextSingle>(context.data)) {
  998. return;
  999. } else if (v::is<StoriesContextSaved>(context.data)
  1000. || v::is<StoriesContextArchive>(context.data)) {
  1001. return;
  1002. }
  1003. const auto i = _all.find(id.peer);
  1004. if (i == end(_all)) {
  1005. return;
  1006. }
  1007. const auto j = i->second.ids.lower_bound(StoryIdDates{ id.story });
  1008. if (j == end(i->second.ids) || j->id != id.story) {
  1009. return;
  1010. }
  1011. const auto ignore = [&] {
  1012. const auto side = kIgnorePreloadAroundIfLoaded;
  1013. const auto left = ranges::min(int(j - begin(i->second.ids)), side);
  1014. const auto right = ranges::min(int(end(i->second.ids) - j), side);
  1015. for (auto k = j - left; k != j + right; ++k) {
  1016. const auto maybeStory = lookup({ id.peer, k->id });
  1017. if (!maybeStory && maybeStory.error() == NoStory::Unknown) {
  1018. return false;
  1019. }
  1020. }
  1021. return true;
  1022. }();
  1023. if (ignore) {
  1024. return;
  1025. }
  1026. const auto side = kPreloadAroundCount;
  1027. const auto left = ranges::min(int(j - begin(i->second.ids)), side);
  1028. const auto right = ranges::min(int(end(i->second.ids) - j), side);
  1029. const auto from = j - left;
  1030. const auto till = j + right;
  1031. for (auto k = from; k != till; ++k) {
  1032. resolve({ id.peer, k->id }, nullptr);
  1033. }
  1034. }
  1035. void Stories::markAsRead(FullStoryId id, bool viewed) {
  1036. if (id.peer == _owner->session().userPeerId()) {
  1037. return;
  1038. }
  1039. const auto maybeStory = lookup(id);
  1040. if (!maybeStory) {
  1041. return;
  1042. }
  1043. const auto story = *maybeStory;
  1044. if (story->expired() && story->inProfile()) {
  1045. _incrementViewsPending[id.peer].emplace(id.story);
  1046. if (!_incrementViewsTimer.isActive()) {
  1047. _incrementViewsTimer.callOnce(kIncrementViewsDelay);
  1048. }
  1049. }
  1050. if (!bumpReadTill(id.peer, id.story)) {
  1051. return;
  1052. }
  1053. if (!_markReadPending.contains(id.peer)) {
  1054. sendMarkAsReadRequests();
  1055. }
  1056. _markReadPending.emplace(id.peer);
  1057. _markReadTimer.callOnce(kMarkAsReadDelay);
  1058. }
  1059. bool Stories::bumpReadTill(PeerId peerId, StoryId maxReadTill) {
  1060. auto &till = _readTill[peerId];
  1061. auto refreshItems = std::vector<StoryId>();
  1062. const auto guard = gsl::finally([&] {
  1063. for (const auto id : refreshItems) {
  1064. _owner->refreshStoryItemViews({ peerId, id });
  1065. }
  1066. });
  1067. if (till < maxReadTill) {
  1068. const auto from = till;
  1069. till = maxReadTill;
  1070. updatePeerStoriesState(_owner->peer(peerId));
  1071. const auto i = _stories.find(peerId);
  1072. if (i != end(_stories)) {
  1073. refreshItems = ranges::make_subrange(
  1074. i->second.lower_bound(from + 1),
  1075. i->second.lower_bound(till + 1)
  1076. ) | ranges::views::transform([=](const auto &pair) {
  1077. _owner->session().changes().storyUpdated(
  1078. pair.second.get(),
  1079. StoryUpdate::Flag::MarkRead);
  1080. return pair.first;
  1081. }) | ranges::to_vector;
  1082. }
  1083. }
  1084. const auto i = _all.find(peerId);
  1085. if (i == end(_all) || i->second.readTill >= maxReadTill) {
  1086. return false;
  1087. }
  1088. const auto wasUnreadCount = i->second.unreadCount();
  1089. i->second.readTill = maxReadTill;
  1090. const auto nowUnreadCount = i->second.unreadCount();
  1091. if (wasUnreadCount != nowUnreadCount) {
  1092. const auto refreshInList = [&](StorySourcesList list) {
  1093. auto &sources = _sources[static_cast<int>(list)];
  1094. const auto i = ranges::find(
  1095. sources,
  1096. peerId,
  1097. &StoriesSourceInfo::id);
  1098. if (i != end(sources)) {
  1099. i->unreadCount = nowUnreadCount;
  1100. sort(list);
  1101. }
  1102. };
  1103. refreshInList(StorySourcesList::NotHidden);
  1104. refreshInList(StorySourcesList::Hidden);
  1105. }
  1106. return true;
  1107. }
  1108. void Stories::toggleHidden(
  1109. PeerId peerId,
  1110. bool hidden,
  1111. std::shared_ptr<Ui::Show> show) {
  1112. const auto peer = _owner->peer(peerId);
  1113. const auto justRemove = peer->isServiceUser() && hidden;
  1114. if (peer->hasStoriesHidden() != hidden) {
  1115. if (!justRemove) {
  1116. peer->setStoriesHidden(hidden);
  1117. }
  1118. session().api().request(MTPstories_TogglePeerStoriesHidden(
  1119. peer->input,
  1120. MTP_bool(hidden)
  1121. )).send();
  1122. }
  1123. const auto name = peer->shortName();
  1124. const auto guard = gsl::finally([&] {
  1125. if (show && !justRemove) {
  1126. const auto phrase = hidden
  1127. ? tr::lng_stories_hidden_to_contacts
  1128. : tr::lng_stories_shown_in_chats;
  1129. show->showToast(phrase(
  1130. tr::now,
  1131. lt_user,
  1132. Ui::Text::Bold(name),
  1133. Ui::Text::RichLangValue));
  1134. }
  1135. });
  1136. if (justRemove) {
  1137. apply(peer, nullptr);
  1138. return;
  1139. }
  1140. const auto i = _all.find(peerId);
  1141. if (i == end(_all)) {
  1142. return;
  1143. }
  1144. i->second.hidden = hidden;
  1145. const auto info = i->second.info();
  1146. const auto main = static_cast<int>(StorySourcesList::NotHidden);
  1147. const auto other = static_cast<int>(StorySourcesList::Hidden);
  1148. const auto proj = &StoriesSourceInfo::id;
  1149. if (hidden) {
  1150. const auto i = ranges::find(_sources[main], peerId, proj);
  1151. if (i != end(_sources[main])) {
  1152. _sources[main].erase(i);
  1153. notifySourcesChanged(StorySourcesList::NotHidden);
  1154. preloadSourcesChanged(StorySourcesList::NotHidden);
  1155. }
  1156. const auto j = ranges::find(_sources[other], peerId, proj);
  1157. if (j == end(_sources[other])) {
  1158. _sources[other].push_back(info);
  1159. } else {
  1160. *j = info;
  1161. }
  1162. sort(StorySourcesList::Hidden);
  1163. } else {
  1164. const auto i = ranges::find(_sources[other], peerId, proj);
  1165. if (i != end(_sources[other])) {
  1166. _sources[other].erase(i);
  1167. notifySourcesChanged(StorySourcesList::Hidden);
  1168. preloadSourcesChanged(StorySourcesList::Hidden);
  1169. }
  1170. const auto j = ranges::find(_sources[main], peerId, proj);
  1171. if (j == end(_sources[main])) {
  1172. _sources[main].push_back(info);
  1173. } else {
  1174. *j = info;
  1175. }
  1176. sort(StorySourcesList::NotHidden);
  1177. }
  1178. }
  1179. void Stories::sendMarkAsReadRequest(
  1180. not_null<PeerData*> peer,
  1181. StoryId tillId) {
  1182. const auto peerId = peer->id;
  1183. _markReadRequests.emplace(peerId);
  1184. const auto finish = [=] {
  1185. _markReadRequests.remove(peerId);
  1186. if (!_markReadTimer.isActive()
  1187. && _markReadPending.contains(peerId)) {
  1188. sendMarkAsReadRequests();
  1189. }
  1190. checkQuitPreventFinished();
  1191. };
  1192. const auto api = &_owner->session().api();
  1193. api->request(MTPstories_ReadStories(
  1194. peer->input,
  1195. MTP_int(tillId)
  1196. )).done(finish).fail(finish).send();
  1197. }
  1198. void Stories::checkQuitPreventFinished() {
  1199. if (_markReadRequests.empty() && _incrementViewsRequests.empty()) {
  1200. if (Core::Quitting()) {
  1201. LOG(("Stories doesn't prevent quit any more."));
  1202. }
  1203. Core::App().quitPreventFinished();
  1204. }
  1205. }
  1206. void Stories::sendMarkAsReadRequests() {
  1207. _markReadTimer.cancel();
  1208. for (auto i = begin(_markReadPending); i != end(_markReadPending);) {
  1209. const auto peerId = *i;
  1210. if (_markReadRequests.contains(peerId)) {
  1211. ++i;
  1212. continue;
  1213. }
  1214. const auto j = _all.find(peerId);
  1215. if (j != end(_all)) {
  1216. sendMarkAsReadRequest(j->second.peer, j->second.readTill);
  1217. }
  1218. i = _markReadPending.erase(i);
  1219. }
  1220. }
  1221. void Stories::sendIncrementViewsRequests() {
  1222. if (_incrementViewsPending.empty()) {
  1223. return;
  1224. }
  1225. struct Prepared {
  1226. PeerId peer = 0;
  1227. QVector<MTPint> ids;
  1228. };
  1229. auto prepared = std::vector<Prepared>();
  1230. for (const auto &[peer, ids] : _incrementViewsPending) {
  1231. if (_incrementViewsRequests.contains(peer)) {
  1232. continue;
  1233. }
  1234. prepared.push_back({ .peer = peer });
  1235. for (const auto &id : ids) {
  1236. prepared.back().ids.push_back(MTP_int(id));
  1237. }
  1238. }
  1239. const auto api = &_owner->session().api();
  1240. for (auto &[peer, ids] : prepared) {
  1241. _incrementViewsRequests.emplace(peer);
  1242. const auto finish = [=, peer = peer] {
  1243. _incrementViewsRequests.remove(peer);
  1244. if (!_incrementViewsTimer.isActive()
  1245. && _incrementViewsPending.contains(peer)) {
  1246. sendIncrementViewsRequests();
  1247. }
  1248. checkQuitPreventFinished();
  1249. };
  1250. api->request(MTPstories_IncrementStoryViews(
  1251. _owner->peer(peer)->input,
  1252. MTP_vector<MTPint>(std::move(ids))
  1253. )).done(finish).fail(finish).send();
  1254. _incrementViewsPending.remove(peer);
  1255. }
  1256. }
  1257. void Stories::loadViewsSlice(
  1258. not_null<PeerData*> peer,
  1259. StoryId id,
  1260. QString offset,
  1261. Fn<void(StoryViews)> done) {
  1262. Expects(peer->isSelf() || !done);
  1263. if (_viewsStoryPeer == peer
  1264. && _viewsStoryId == id
  1265. && _viewsOffset == offset
  1266. && (!offset.isEmpty() || _viewsRequestId)) {
  1267. if (_viewsRequestId) {
  1268. _viewsDone = std::move(done);
  1269. }
  1270. return;
  1271. }
  1272. _viewsStoryPeer = peer;
  1273. _viewsStoryId = id;
  1274. _viewsOffset = offset;
  1275. _viewsDone = std::move(done);
  1276. if (peer->isSelf()) {
  1277. sendViewsSliceRequest();
  1278. } else {
  1279. sendViewsCountsRequest();
  1280. }
  1281. }
  1282. void Stories::loadReactionsSlice(
  1283. not_null<PeerData*> peer,
  1284. StoryId id,
  1285. QString offset,
  1286. Fn<void(StoryViews)> done) {
  1287. Expects(peer->isChannel());
  1288. if (_reactionsStoryPeer == peer
  1289. && _reactionsStoryId == id
  1290. && _reactionsOffset == offset) {
  1291. if (_reactionsRequestId) {
  1292. _reactionsDone = std::move(done);
  1293. }
  1294. return;
  1295. }
  1296. _reactionsStoryPeer = peer;
  1297. _reactionsStoryId = id;
  1298. _reactionsOffset = offset;
  1299. _reactionsDone = std::move(done);
  1300. using Flag = MTPstories_GetStoryReactionsList::Flag;
  1301. const auto api = &_owner->session().api();
  1302. _owner->session().api().request(_reactionsRequestId).cancel();
  1303. _reactionsRequestId = api->request(MTPstories_GetStoryReactionsList(
  1304. MTP_flags(offset.isEmpty() ? Flag() : Flag::f_offset),
  1305. _reactionsStoryPeer->input,
  1306. MTP_int(_reactionsStoryId),
  1307. MTPReaction(),
  1308. MTP_string(_reactionsOffset),
  1309. MTP_int(kViewsPerPage)
  1310. )).done([=](const MTPstories_StoryReactionsList &result) {
  1311. _reactionsRequestId = 0;
  1312. const auto &data = result.data();
  1313. auto slice = StoryViews{
  1314. .nextOffset = data.vnext_offset().value_or_empty(),
  1315. .reactions = data.vcount().v,
  1316. .total = data.vcount().v,
  1317. };
  1318. _owner->processUsers(data.vusers());
  1319. _owner->processChats(data.vchats());
  1320. slice.list.reserve(data.vreactions().v.size());
  1321. for (const auto &reaction : data.vreactions().v) {
  1322. reaction.match([&](const MTPDstoryReaction &data) {
  1323. slice.list.push_back({
  1324. .peer = _owner->peer(peerFromMTP(data.vpeer_id())),
  1325. .reaction = ReactionFromMTP(data.vreaction()),
  1326. .date = data.vdate().v,
  1327. });
  1328. }, [&](const MTPDstoryReactionPublicRepost &data) {
  1329. const auto story = applySingle(
  1330. peerFromMTP(data.vpeer_id()),
  1331. data.vstory());
  1332. if (story) {
  1333. slice.list.push_back({
  1334. .peer = story->peer(),
  1335. .repostId = story->id(),
  1336. });
  1337. }
  1338. }, [&](const MTPDstoryReactionPublicForward &data) {
  1339. const auto item = _owner->addNewMessage(
  1340. data.vmessage(),
  1341. {},
  1342. NewMessageType::Existing);
  1343. if (item) {
  1344. slice.list.push_back({
  1345. .peer = item->history()->peer,
  1346. .forwardId = item->id,
  1347. });
  1348. }
  1349. });
  1350. }
  1351. const auto fullId = FullStoryId{
  1352. .peer = _reactionsStoryPeer->id,
  1353. .story = _reactionsStoryId,
  1354. };
  1355. if (const auto story = lookup(fullId)) {
  1356. (*story)->applyChannelReactionsSlice(_reactionsOffset, slice);
  1357. }
  1358. if (const auto done = base::take(_reactionsDone)) {
  1359. done(std::move(slice));
  1360. }
  1361. }).fail([=] {
  1362. _reactionsRequestId = 0;
  1363. if (const auto done = base::take(_reactionsDone)) {
  1364. done({});
  1365. }
  1366. }).send();
  1367. }
  1368. void Stories::sendViewsSliceRequest() {
  1369. Expects(_viewsStoryPeer != nullptr);
  1370. Expects(_viewsStoryPeer->isSelf());
  1371. using Flag = MTPstories_GetStoryViewsList::Flag;
  1372. const auto api = &_owner->session().api();
  1373. _owner->session().api().request(_viewsRequestId).cancel();
  1374. _viewsRequestId = api->request(MTPstories_GetStoryViewsList(
  1375. MTP_flags(Flag::f_reactions_first),
  1376. _viewsStoryPeer->input,
  1377. MTPstring(), // q
  1378. MTP_int(_viewsStoryId),
  1379. MTP_string(_viewsOffset),
  1380. MTP_int(_viewsDone ? kViewsPerPage : kPollingViewsPerPage)
  1381. )).done([=](const MTPstories_StoryViewsList &result) {
  1382. _viewsRequestId = 0;
  1383. const auto &data = result.data();
  1384. auto slice = StoryViews{
  1385. .nextOffset = data.vnext_offset().value_or_empty(),
  1386. .reactions = data.vreactions_count().v,
  1387. .forwards = data.vforwards_count().v,
  1388. .views = data.vviews_count().v,
  1389. .total = data.vcount().v,
  1390. };
  1391. _owner->processUsers(data.vusers());
  1392. _owner->processChats(data.vchats());
  1393. slice.list.reserve(data.vviews().v.size());
  1394. for (const auto &view : data.vviews().v) {
  1395. view.match([&](const MTPDstoryView &data) {
  1396. slice.list.push_back({
  1397. .peer = _owner->peer(peerFromUser(data.vuser_id())),
  1398. .reaction = (data.vreaction()
  1399. ? ReactionFromMTP(*data.vreaction())
  1400. : Data::ReactionId()),
  1401. .date = data.vdate().v,
  1402. });
  1403. }, [&](const MTPDstoryViewPublicRepost &data) {
  1404. const auto story = applySingle(
  1405. peerFromMTP(data.vpeer_id()),
  1406. data.vstory());
  1407. if (story) {
  1408. slice.list.push_back({
  1409. .peer = story->peer(),
  1410. .repostId = story->id(),
  1411. });
  1412. }
  1413. }, [&](const MTPDstoryViewPublicForward &data) {
  1414. const auto item = _owner->addNewMessage(
  1415. data.vmessage(),
  1416. {},
  1417. NewMessageType::Existing);
  1418. if (item) {
  1419. slice.list.push_back({
  1420. .peer = item->history()->peer,
  1421. .forwardId = item->id,
  1422. });
  1423. }
  1424. });
  1425. }
  1426. const auto fullId = FullStoryId{
  1427. .peer = _owner->session().userPeerId(),
  1428. .story = _viewsStoryId,
  1429. };
  1430. if (const auto story = lookup(fullId)) {
  1431. (*story)->applyViewsSlice(_viewsOffset, slice);
  1432. }
  1433. if (const auto done = base::take(_viewsDone)) {
  1434. done(std::move(slice));
  1435. }
  1436. }).fail([=] {
  1437. _viewsRequestId = 0;
  1438. if (const auto done = base::take(_viewsDone)) {
  1439. done({});
  1440. }
  1441. }).send();
  1442. }
  1443. void Stories::sendViewsCountsRequest() {
  1444. Expects(_viewsStoryPeer != nullptr);
  1445. Expects(!_viewsDone);
  1446. const auto api = &_owner->session().api();
  1447. _owner->session().api().request(_viewsRequestId).cancel();
  1448. _viewsRequestId = api->request(MTPstories_GetStoriesViews(
  1449. _viewsStoryPeer->input,
  1450. MTP_vector<MTPint>(1, MTP_int(_viewsStoryId))
  1451. )).done([=](const MTPstories_StoryViews &result) {
  1452. _viewsRequestId = 0;
  1453. const auto &data = result.data();
  1454. _owner->processUsers(data.vusers());
  1455. if (data.vviews().v.size() == 1) {
  1456. const auto fullId = FullStoryId{
  1457. _viewsStoryPeer->id,
  1458. _viewsStoryId,
  1459. };
  1460. if (const auto story = lookup(fullId)) {
  1461. (*story)->applyViewsCounts(data.vviews().v.front().data());
  1462. }
  1463. }
  1464. }).fail([=] {
  1465. _viewsRequestId = 0;
  1466. }).send();
  1467. }
  1468. bool Stories::hasArchive(not_null<PeerData*> peer) const {
  1469. if (peer->isSelf()) {
  1470. return true;
  1471. } else if (const auto channel = peer->asChannel()) {
  1472. return channel->canEditStories();
  1473. }
  1474. return false;
  1475. }
  1476. const StoriesIds &Stories::archive(PeerId peerId) const {
  1477. static const auto empty = StoriesIds();
  1478. const auto i = _archive.find(peerId);
  1479. return (i != end(_archive)) ? i->second.ids : empty;
  1480. }
  1481. rpl::producer<PeerId> Stories::archiveChanged() const {
  1482. return _archiveChanged.events();
  1483. }
  1484. int Stories::archiveCount(PeerId peerId) const {
  1485. const auto i = _archive.find(peerId);
  1486. return (i != end(_archive)) ? i->second.total : 0;
  1487. }
  1488. bool Stories::archiveCountKnown(PeerId peerId) const {
  1489. const auto i = _archive.find(peerId);
  1490. return (i != end(_archive)) && (i->second.total >= 0);
  1491. }
  1492. bool Stories::archiveLoaded(PeerId peerId) const {
  1493. const auto i = _archive.find(peerId);
  1494. return (i != end(_archive)) && i->second.loaded;
  1495. }
  1496. const StoriesIds &Stories::saved(PeerId peerId) const {
  1497. static const auto empty = StoriesIds();
  1498. const auto i = _saved.find(peerId);
  1499. return (i != end(_saved)) ? i->second.ids : empty;
  1500. }
  1501. rpl::producer<PeerId> Stories::savedChanged() const {
  1502. return _savedChanged.events();
  1503. }
  1504. int Stories::savedCount(PeerId peerId) const {
  1505. const auto i = _saved.find(peerId);
  1506. return (i != end(_saved)) ? i->second.total : 0;
  1507. }
  1508. bool Stories::savedCountKnown(PeerId peerId) const {
  1509. const auto i = _saved.find(peerId);
  1510. return (i != end(_saved)) && (i->second.total >= 0);
  1511. }
  1512. bool Stories::savedLoaded(PeerId peerId) const {
  1513. const auto i = _saved.find(peerId);
  1514. return (i != end(_saved)) && i->second.loaded;
  1515. }
  1516. void Stories::archiveLoadMore(PeerId peerId) {
  1517. const auto peer = _owner->peer(peerId);
  1518. const auto archive = lookupArchive(peer);
  1519. if (!archive || archive->requestId || archive->loaded) {
  1520. return;
  1521. }
  1522. const auto api = &_owner->session().api();
  1523. archive->requestId = api->request(MTPstories_GetStoriesArchive(
  1524. peer->input,
  1525. MTP_int(archive->lastId),
  1526. MTP_int(archive->lastId ? kArchivePerPage : kArchiveFirstPerPage)
  1527. )).done([=](const MTPstories_Stories &result) {
  1528. const auto archive = lookupArchive(peer);
  1529. if (!archive) {
  1530. return;
  1531. }
  1532. archive->requestId = 0;
  1533. const auto &data = result.data();
  1534. const auto now = base::unixtime::now();
  1535. archive->total = data.vcount().v;
  1536. for (const auto &story : data.vstories().v) {
  1537. const auto id = story.match([&](const auto &id) {
  1538. return id.vid().v;
  1539. });
  1540. archive->ids.list.emplace(id);
  1541. archive->lastId = id;
  1542. if (!parseAndApply(peer, story, now)) {
  1543. archive->ids.list.remove(id);
  1544. if (archive->total > 0) {
  1545. --archive->total;
  1546. }
  1547. }
  1548. }
  1549. const auto ids = int(archive->ids.list.size());
  1550. archive->loaded = data.vstories().v.empty();
  1551. archive->total = archive->loaded ? ids : std::max(archive->total, ids);
  1552. _archiveChanged.fire_copy(peerId);
  1553. }).fail([=] {
  1554. const auto archive = lookupArchive(peer);
  1555. if (!archive) {
  1556. return;
  1557. }
  1558. archive->requestId = 0;
  1559. archive->loaded = true;
  1560. archive->total = int(archive->ids.list.size());
  1561. _archiveChanged.fire_copy(peerId);
  1562. }).send();
  1563. }
  1564. void Stories::savedLoadMore(PeerId peerId) {
  1565. auto &saved = _saved[peerId];
  1566. if (saved.requestId || saved.loaded) {
  1567. return;
  1568. }
  1569. const auto api = &_owner->session().api();
  1570. const auto peer = _owner->peer(peerId);
  1571. saved.requestId = api->request(MTPstories_GetPinnedStories(
  1572. peer->input,
  1573. MTP_int(saved.lastId),
  1574. MTP_int(saved.lastId ? kSavedPerPage : kSavedFirstPerPage)
  1575. )).done([=](const MTPstories_Stories &result) {
  1576. auto &saved = _saved[peerId];
  1577. saved.requestId = 0;
  1578. const auto &data = result.data();
  1579. const auto now = base::unixtime::now();
  1580. auto pinnedToTopIds = data.vpinned_to_top().value_or_empty();
  1581. auto pinnedToTop = pinnedToTopIds
  1582. | ranges::views::transform(&MTPint::v)
  1583. | ranges::to_vector;
  1584. saved.total = data.vcount().v;
  1585. for (const auto &story : data.vstories().v) {
  1586. const auto id = story.match([&](const auto &id) {
  1587. return id.vid().v;
  1588. });
  1589. saved.ids.list.emplace(id);
  1590. saved.lastId = id;
  1591. if (!parseAndApply(peer, story, now)) {
  1592. saved.ids.list.remove(id);
  1593. if (saved.total > 0) {
  1594. --saved.total;
  1595. }
  1596. }
  1597. }
  1598. const auto ids = int(saved.ids.list.size());
  1599. saved.loaded = data.vstories().v.empty();
  1600. saved.total = saved.loaded ? ids : std::max(saved.total, ids);
  1601. setPinnedToTop(peerId, std::move(pinnedToTop));
  1602. _savedChanged.fire_copy(peerId);
  1603. }).fail([=] {
  1604. auto &saved = _saved[peerId];
  1605. saved.requestId = 0;
  1606. saved.loaded = true;
  1607. saved.total = int(saved.ids.list.size());
  1608. _savedChanged.fire_copy(peerId);
  1609. }).send();
  1610. }
  1611. void Stories::setPinnedToTop(
  1612. PeerId peerId,
  1613. std::vector<StoryId> &&pinnedToTop) {
  1614. const auto i = _saved.find(peerId);
  1615. if (i == end(_saved) && pinnedToTop.empty()) {
  1616. return;
  1617. }
  1618. auto &saved = (i == end(_saved)) ? _saved[peerId] : i->second;
  1619. if (saved.ids.pinnedToTop != pinnedToTop) {
  1620. for (const auto id : saved.ids.pinnedToTop) {
  1621. if (!ranges::contains(pinnedToTop, id)) {
  1622. if (const auto maybeStory = lookup({ peerId, id })) {
  1623. (*maybeStory)->setPinnedToTop(false);
  1624. }
  1625. }
  1626. }
  1627. for (const auto id : pinnedToTop) {
  1628. if (!ranges::contains(saved.ids.pinnedToTop, id)) {
  1629. if (const auto maybeStory = lookup({ peerId, id })) {
  1630. (*maybeStory)->setPinnedToTop(true);
  1631. }
  1632. }
  1633. }
  1634. saved.ids.pinnedToTop = std::move(pinnedToTop);
  1635. }
  1636. }
  1637. void Stories::deleteList(const std::vector<FullStoryId> &ids) {
  1638. if (ids.empty()) {
  1639. return;
  1640. }
  1641. const auto peer = session().data().peer(ids.front().peer);
  1642. auto list = QVector<MTPint>();
  1643. list.reserve(ids.size());
  1644. for (const auto &id : ids) {
  1645. if (id.peer == peer->id) {
  1646. list.push_back(MTP_int(id.story));
  1647. }
  1648. }
  1649. const auto api = &_owner->session().api();
  1650. api->request(MTPstories_DeleteStories(
  1651. peer->input,
  1652. MTP_vector<MTPint>(list)
  1653. )).done([=](const MTPVector<MTPint> &result) {
  1654. for (const auto &id : result.v) {
  1655. applyDeleted(peer, id.v);
  1656. }
  1657. }).send();
  1658. }
  1659. void Stories::toggleInProfileList(
  1660. const std::vector<FullStoryId> &ids,
  1661. bool inProfile) {
  1662. if (ids.empty()) {
  1663. return;
  1664. }
  1665. const auto peer = session().data().peer(ids.front().peer);
  1666. auto list = QVector<MTPint>();
  1667. list.reserve(ids.size());
  1668. for (const auto &id : ids) {
  1669. if (id.peer == peer->id) {
  1670. list.push_back(MTP_int(id.story));
  1671. }
  1672. }
  1673. if (list.empty()) {
  1674. return;
  1675. }
  1676. const auto api = &_owner->session().api();
  1677. api->request(MTPstories_TogglePinned(
  1678. peer->input,
  1679. MTP_vector<MTPint>(list),
  1680. MTP_bool(inProfile)
  1681. )).done([=](const MTPVector<MTPint> &result) {
  1682. const auto peerId = peer->id;
  1683. auto &saved = _saved[peerId];
  1684. const auto loaded = saved.loaded;
  1685. const auto lastId = !saved.ids.list.empty()
  1686. ? saved.ids.list.back()
  1687. : saved.lastId
  1688. ? saved.lastId
  1689. : std::numeric_limits<StoryId>::max();
  1690. auto dirty = false;
  1691. for (const auto &id : result.v) {
  1692. if (const auto maybeStory = lookup({ peerId, id.v })) {
  1693. const auto story = *maybeStory;
  1694. story->setInProfile(inProfile);
  1695. if (inProfile) {
  1696. const auto add = loaded || (id.v >= lastId);
  1697. if (!add) {
  1698. dirty = true;
  1699. } else if (saved.ids.list.emplace(id.v).second) {
  1700. if (saved.total >= 0) {
  1701. ++saved.total;
  1702. }
  1703. }
  1704. } else if (saved.ids.list.remove(id.v)) {
  1705. if (saved.total > 0) {
  1706. --saved.total;
  1707. }
  1708. } else if (!loaded) {
  1709. dirty = true;
  1710. }
  1711. } else if (!loaded) {
  1712. dirty = true;
  1713. }
  1714. }
  1715. if (dirty) {
  1716. savedLoadMore(peerId);
  1717. } else {
  1718. _savedChanged.fire_copy(peerId);
  1719. }
  1720. }).send();
  1721. }
  1722. bool Stories::canTogglePinnedList(
  1723. const std::vector<FullStoryId> &ids,
  1724. bool pin) const {
  1725. Expects(!ids.empty());
  1726. if (!pin) {
  1727. return true;
  1728. }
  1729. const auto peerId = ids.front().peer;
  1730. const auto i = _saved.find(peerId);
  1731. if (i == end(_saved)) {
  1732. return false;
  1733. }
  1734. auto &already = i->second.ids.pinnedToTop;
  1735. auto count = int(already.size());
  1736. for (const auto &id : ids) {
  1737. if (!ranges::contains(already, id.story)) {
  1738. ++count;
  1739. }
  1740. }
  1741. return count <= maxPinnedCount();
  1742. }
  1743. int Stories::maxPinnedCount() const {
  1744. const auto appConfig = &_owner->session().appConfig();
  1745. return appConfig->get<int>(u"stories_pinned_to_top_count_max"_q, 3);
  1746. }
  1747. void Stories::togglePinnedList(
  1748. const std::vector<FullStoryId> &ids,
  1749. bool pin) {
  1750. if (ids.empty()) {
  1751. return;
  1752. }
  1753. const auto peerId = ids.front().peer;
  1754. auto &saved = _saved[peerId];
  1755. auto list = QVector<MTPint>();
  1756. list.reserve(maxPinnedCount());
  1757. for (const auto &id : saved.ids.pinnedToTop) {
  1758. if (pin || !ranges::contains(ids, FullStoryId{ peerId, id })) {
  1759. list.push_back(MTP_int(id));
  1760. }
  1761. }
  1762. if (pin) {
  1763. auto copy = ids;
  1764. ranges::sort(copy, ranges::greater());
  1765. for (const auto &id : copy) {
  1766. if (id.peer == peerId
  1767. && !ranges::contains(saved.ids.pinnedToTop, id.story)) {
  1768. list.push_back(MTP_int(id.story));
  1769. }
  1770. }
  1771. }
  1772. const auto api = &_owner->session().api();
  1773. const auto peer = session().data().peer(peerId);
  1774. api->request(MTPstories_TogglePinnedToTop(
  1775. peer->input,
  1776. MTP_vector<MTPint>(list)
  1777. )).done([=] {
  1778. setPinnedToTop(peerId, list
  1779. | ranges::views::transform(&MTPint::v)
  1780. | ranges::to_vector);
  1781. _savedChanged.fire_copy(peerId);
  1782. }).send();
  1783. }
  1784. bool Stories::isQuitPrevent() {
  1785. if (!_markReadPending.empty()) {
  1786. sendMarkAsReadRequests();
  1787. }
  1788. if (!_incrementViewsPending.empty()) {
  1789. sendIncrementViewsRequests();
  1790. }
  1791. if (_markReadRequests.empty() && _incrementViewsRequests.empty()) {
  1792. return false;
  1793. }
  1794. LOG(("Stories prevents quit, marking as read..."));
  1795. return true;
  1796. }
  1797. void Stories::incrementPreloadingMainSources() {
  1798. Expects(_preloadingMainSourcesCounter >= 0);
  1799. if (++_preloadingMainSourcesCounter == 1
  1800. && rebuildPreloadSources(StorySourcesList::NotHidden)) {
  1801. continuePreloading();
  1802. }
  1803. }
  1804. void Stories::decrementPreloadingMainSources() {
  1805. Expects(_preloadingMainSourcesCounter > 0);
  1806. if (!--_preloadingMainSourcesCounter
  1807. && rebuildPreloadSources(StorySourcesList::NotHidden)) {
  1808. continuePreloading();
  1809. }
  1810. }
  1811. void Stories::incrementPreloadingHiddenSources() {
  1812. Expects(_preloadingHiddenSourcesCounter >= 0);
  1813. if (++_preloadingHiddenSourcesCounter == 1
  1814. && rebuildPreloadSources(StorySourcesList::Hidden)) {
  1815. continuePreloading();
  1816. }
  1817. }
  1818. void Stories::decrementPreloadingHiddenSources() {
  1819. Expects(_preloadingHiddenSourcesCounter > 0);
  1820. if (!--_preloadingHiddenSourcesCounter
  1821. && rebuildPreloadSources(StorySourcesList::Hidden)) {
  1822. continuePreloading();
  1823. }
  1824. }
  1825. void Stories::setPreloadingInViewer(std::vector<FullStoryId> ids) {
  1826. ids.erase(ranges::remove_if(ids, [&](FullStoryId id) {
  1827. return _preloaded.contains(id);
  1828. }), end(ids));
  1829. if (_toPreloadViewer != ids) {
  1830. _toPreloadViewer = std::move(ids);
  1831. continuePreloading();
  1832. }
  1833. }
  1834. std::optional<Stories::PeerSourceState> Stories::peerSourceState(
  1835. not_null<PeerData*> peer,
  1836. StoryId storyMaxId) {
  1837. const auto i = _readTill.find(peer->id);
  1838. if (_readTillReceived || (i != end(_readTill))) {
  1839. return PeerSourceState{
  1840. .maxId = storyMaxId,
  1841. .readTill = std::min(
  1842. storyMaxId,
  1843. (i != end(_readTill)) ? i->second : 0),
  1844. };
  1845. }
  1846. requestReadTills();
  1847. _pendingPeerStateMaxId[peer] = storyMaxId;
  1848. return std::nullopt;
  1849. }
  1850. void Stories::requestReadTills() {
  1851. if (_readTillReceived || _readTillsRequestId) {
  1852. return;
  1853. }
  1854. const auto api = &_owner->session().api();
  1855. _readTillsRequestId = api->request(MTPstories_GetAllReadPeerStories(
  1856. )).done([=](const MTPUpdates &result) {
  1857. _readTillReceived = true;
  1858. api->applyUpdates(result);
  1859. for (auto &[peer, maxId] : base::take(_pendingPeerStateMaxId)) {
  1860. updatePeerStoriesState(peer);
  1861. }
  1862. for (const auto &storyId : base::take(_pendingReadTillItems)) {
  1863. _owner->refreshStoryItemViews(storyId);
  1864. }
  1865. }).send();
  1866. }
  1867. bool Stories::isUnread(not_null<Story*> story) {
  1868. const auto till = _readTill.find(story->peer()->id);
  1869. if (till == end(_readTill) && !_readTillReceived) {
  1870. requestReadTills();
  1871. _pendingReadTillItems.emplace(story->fullId());
  1872. return false;
  1873. }
  1874. const auto readTill = (till != end(_readTill)) ? till->second : 0;
  1875. return (story->id() > readTill);
  1876. }
  1877. void Stories::registerPolling(not_null<Story*> story, Polling polling) {
  1878. auto &settings = _pollingSettings[story];
  1879. switch (polling) {
  1880. case Polling::Chat: ++settings.chat; break;
  1881. case Polling::Viewer:
  1882. ++settings.viewer;
  1883. if ((story->peer()->isSelf() || story->peer()->isChannel())
  1884. && _pollingViews.emplace(story).second) {
  1885. sendPollingViewsRequests();
  1886. }
  1887. break;
  1888. }
  1889. maybeSchedulePolling(story, settings, base::unixtime::now());
  1890. }
  1891. void Stories::unregisterPolling(not_null<Story*> story, Polling polling) {
  1892. const auto i = _pollingSettings.find(story);
  1893. Assert(i != end(_pollingSettings));
  1894. switch (polling) {
  1895. case Polling::Chat:
  1896. Assert(i->second.chat > 0);
  1897. --i->second.chat;
  1898. break;
  1899. case Polling::Viewer:
  1900. Assert(i->second.viewer > 0);
  1901. if (!--i->second.viewer) {
  1902. _pollingViews.remove(story);
  1903. if (_pollingViews.empty()) {
  1904. _pollingViewsTimer.cancel();
  1905. }
  1906. }
  1907. break;
  1908. }
  1909. if (!i->second.chat && !i->second.viewer) {
  1910. _pollingSettings.erase(i);
  1911. }
  1912. }
  1913. bool Stories::registerPolling(FullStoryId id, Polling polling) {
  1914. if (const auto maybeStory = lookup(id)) {
  1915. registerPolling(*maybeStory, polling);
  1916. return true;
  1917. }
  1918. return false;
  1919. }
  1920. void Stories::unregisterPolling(FullStoryId id, Polling polling) {
  1921. if (const auto maybeStory = lookup(id)) {
  1922. unregisterPolling(*maybeStory, polling);
  1923. } else if (const auto i = _deletingStories.find(id)
  1924. ; i != end(_deletingStories)) {
  1925. unregisterPolling(i->second.get(), polling);
  1926. } else {
  1927. Unexpected("Couldn't find story for unregistering polling.");
  1928. }
  1929. }
  1930. int Stories::pollingInterval(const PollingSettings &settings) const {
  1931. return settings.viewer ? kPollingIntervalViewer : kPollingIntervalChat;
  1932. }
  1933. void Stories::maybeSchedulePolling(
  1934. not_null<Story*> story,
  1935. const PollingSettings &settings,
  1936. TimeId now) {
  1937. const auto last = story->lastUpdateTime();
  1938. const auto next = last + pollingInterval(settings);
  1939. const auto left = std::max(next - now, 0) * crl::time(1000) + 1;
  1940. if (!_pollingTimer.isActive() || _pollingTimer.remainingTime() > left) {
  1941. _pollingTimer.callOnce(left);
  1942. }
  1943. }
  1944. void Stories::sendPollingRequests() {
  1945. auto min = 0;
  1946. const auto now = base::unixtime::now();
  1947. for (const auto &[story, settings] : _pollingSettings) {
  1948. const auto last = story->lastUpdateTime();
  1949. const auto next = last + pollingInterval(settings);
  1950. if (now >= next) {
  1951. resolve(story->fullId(), nullptr, true);
  1952. } else {
  1953. const auto left = (next - now) * crl::time(1000) + 1;
  1954. if (!min || left < min) {
  1955. min = left;
  1956. }
  1957. }
  1958. }
  1959. if (min > 0) {
  1960. _pollingTimer.callOnce(min);
  1961. }
  1962. }
  1963. void Stories::sendPollingViewsRequests() {
  1964. if (_pollingViews.empty()) {
  1965. return;
  1966. } else if (!_viewsRequestId) {
  1967. Assert(_viewsDone == nullptr);
  1968. const auto story = _pollingViews.front();
  1969. loadViewsSlice(story->peer(), story->id(), QString(), nullptr);
  1970. }
  1971. _pollingViewsTimer.callOnce(kPollViewsInterval);
  1972. }
  1973. void Stories::updatePeerStoriesState(not_null<PeerData*> peer) {
  1974. const auto till = _readTill.find(peer->id);
  1975. const auto readTill = (till != end(_readTill)) ? till->second : 0;
  1976. const auto pendingMaxId = [&] {
  1977. const auto j = _pendingPeerStateMaxId.find(peer);
  1978. return (j != end(_pendingPeerStateMaxId)) ? j->second : 0;
  1979. };
  1980. const auto i = _all.find(peer->id);
  1981. const auto max = (i != end(_all))
  1982. ? (i->second.ids.empty() ? 0 : i->second.ids.back().id)
  1983. : pendingMaxId();
  1984. peer->setStoriesState(!max
  1985. ? PeerData::StoriesState::None
  1986. : (max <= readTill)
  1987. ? PeerData::StoriesState::HasRead
  1988. : PeerData::StoriesState::HasUnread);
  1989. }
  1990. void Stories::preloadSourcesChanged(StorySourcesList list) {
  1991. if (rebuildPreloadSources(list)) {
  1992. continuePreloading();
  1993. }
  1994. }
  1995. bool Stories::rebuildPreloadSources(StorySourcesList list) {
  1996. const auto index = static_cast<int>(list);
  1997. const auto &counter = (list == StorySourcesList::Hidden)
  1998. ? _preloadingHiddenSourcesCounter
  1999. : _preloadingMainSourcesCounter;
  2000. if (!counter) {
  2001. return !base::take(_toPreloadSources[index]).empty();
  2002. }
  2003. auto now = std::vector<FullStoryId>();
  2004. auto processed = 0;
  2005. for (const auto &source : _sources[index]) {
  2006. const auto i = _all.find(source.id);
  2007. if (i != end(_all)) {
  2008. if (const auto id = i->second.toOpen().id) {
  2009. const auto fullId = FullStoryId{ source.id, id };
  2010. if (!_preloaded.contains(fullId)) {
  2011. now.push_back(fullId);
  2012. }
  2013. }
  2014. }
  2015. if (++processed >= kMaxPreloadSources) {
  2016. break;
  2017. }
  2018. }
  2019. if (now != _toPreloadSources[index]) {
  2020. _toPreloadSources[index] = std::move(now);
  2021. return true;
  2022. }
  2023. return false;
  2024. }
  2025. void Stories::continuePreloading() {
  2026. const auto now = _preloading ? _preloading->id() : FullStoryId();
  2027. if (now) {
  2028. if (shouldContinuePreload(now)) {
  2029. return;
  2030. }
  2031. _preloading = nullptr;
  2032. }
  2033. const auto id = nextPreloadId();
  2034. if (!id) {
  2035. return;
  2036. } else if (const auto maybeStory = lookup(id)) {
  2037. startPreloading(*maybeStory);
  2038. }
  2039. }
  2040. bool Stories::shouldContinuePreload(FullStoryId id) const {
  2041. const auto first = ranges::views::concat(
  2042. _toPreloadViewer,
  2043. _toPreloadSources[static_cast<int>(StorySourcesList::Hidden)],
  2044. _toPreloadSources[static_cast<int>(StorySourcesList::NotHidden)]
  2045. ) | ranges::views::take(kStillPreloadFromFirst);
  2046. return ranges::contains(first, id);
  2047. }
  2048. FullStoryId Stories::nextPreloadId() const {
  2049. const auto hidden = static_cast<int>(StorySourcesList::Hidden);
  2050. const auto main = static_cast<int>(StorySourcesList::NotHidden);
  2051. const auto result = !_toPreloadViewer.empty()
  2052. ? _toPreloadViewer.front()
  2053. : !_toPreloadSources[hidden].empty()
  2054. ? _toPreloadSources[hidden].front()
  2055. : !_toPreloadSources[main].empty()
  2056. ? _toPreloadSources[main].front()
  2057. : FullStoryId();
  2058. Ensures(!_preloaded.contains(result));
  2059. return result;
  2060. }
  2061. void Stories::startPreloading(not_null<Story*> story) {
  2062. Expects(!_preloaded.contains(story->fullId()));
  2063. const auto id = story->fullId();
  2064. auto preloading = std::make_unique<StoryPreload>(story, [=] {
  2065. _preloading = nullptr;
  2066. preloadFinished(id, true);
  2067. });
  2068. if (!_preloaded.contains(id)) {
  2069. _preloading = std::move(preloading);
  2070. }
  2071. }
  2072. void Stories::preloadFinished(FullStoryId id, bool markAsPreloaded) {
  2073. for (auto &sources : _toPreloadSources) {
  2074. sources.erase(ranges::remove(sources, id), end(sources));
  2075. }
  2076. _toPreloadViewer.erase(
  2077. ranges::remove(_toPreloadViewer, id),
  2078. end(_toPreloadViewer));
  2079. if (markAsPreloaded) {
  2080. _preloaded.emplace(id);
  2081. }
  2082. crl::on_main(this, [=] {
  2083. continuePreloading();
  2084. });
  2085. }
  2086. } // namespace Data