diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 421e2b443..9313e0b85 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -705,10 +705,11 @@ void Application::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if (cDevVersion() && Local::oldMapVersion() < 9014) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D)); + if (cDevVersion() && Local::oldMapVersion() < 9016) { +// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D)); + versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed(); } else if (Local::oldMapVersion() < 9015) { -// versionFeatures = lang(lng_new_version_text).trimmed(); + versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed(); } else { versionFeatures = lang(lng_new_version_minor).trimmed(); } diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 4926981de..2875cabd1 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -23,7 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org static const int32 AppVersion = 9015; static const wchar_t *AppVersionStr = L"0.9.15"; static const bool DevVersion = false; -#define BETA_VERSION (9015005ULL) // just comment this line to build public version +#define BETA_VERSION (9015006ULL) // just comment this line to build public version static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; static const wchar_t *AppName = L"Telegram Desktop"; @@ -87,6 +87,7 @@ enum { AverageGifSize = 320 * 240, WaitBeforeGifPause = 200, // wait 200ms for gif draw before pausing it InlineBotRequestDelay = 400, // wait 400ms before context bot realtime request + RecentInlineBotsLimit = 10, AVBlockSize = 4096, // 4Kb for ffmpeg blocksize diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index d46b65a4f..ef5247c88 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -163,7 +163,7 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO PeerData *act = App::main()->activePeer(); MsgId actId = App::main()->activeMsgId(); for (; from < to; ++from) { - bool active = ((_filterResults[from]->history->peer == act) || (_filterResults[from]->history->peer->migrateTo() && _filterResults[from]->history->peer->migrateTo() == act)) && !actId; + bool active = ((_filterResults[from]->history->peer == act) || (_filterResults[from]->history->peer->migrateTo() && _filterResults[from]->history->peer->migrateTo() == act)) && !actId; bool selected = (from == _filteredSel) || (_filterResults[from]->history->peer == _menuPeer); _filterResults[from]->paint(p, w, active, selected, paintingOther); p.translate(0, st::dlgHeight); @@ -872,7 +872,7 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) { } _hashtagFilter = newFilter.toString(); if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); } const RecentHashtagPack &recent(cRecentSearchHashtags()); _hashtagResults.clear(); @@ -1385,7 +1385,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (from < _filterResults.size()) { int32 to = (yTo / int32(st::dlgHeight)) + 1, w = width(); if (to > _filterResults.size()) to = _filterResults.size(); - + for (; from < to; ++from) { _filterResults[from]->history->peer->photo->load(); } @@ -1434,7 +1434,7 @@ bool DialogsInner::choosePeer() { } } cSetRecentSearchHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); emit refreshHashtags(); selByMouse = true; @@ -1487,7 +1487,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); recent = cRecentSearchHashtags(); } found = true; @@ -1495,7 +1495,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } if (found) { cSetRecentSearchHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); } } diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index d48301709..85dfa1047 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -3766,7 +3766,7 @@ void MentionsInner::paintEvent(QPaintEvent *e) { if (selected) { p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b); int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2; - if (!_hrows->isEmpty()) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); + if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); } p.setPen(st::black->p); if (!_mrows->isEmpty()) { @@ -3902,6 +3902,10 @@ bool MentionsInner::select() { return false; } +void MentionsInner::setRecentInlineBotsInRows(int32 bots) { + _recentInlineBotsInRows = bots; +} + QString MentionsInner::getSelected() const { int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size()); if (_sel >= 0 && _sel < maxSel) { @@ -3930,20 +3934,32 @@ void MentionsInner::mousePressEvent(QMouseEvent *e) { _mouseSel = true; onUpdateSelected(true); if (e->button() == Qt::LeftButton) { - if (_overDelete && _sel >= 0 && _sel < _hrows->size()) { + if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) { _mousePos = mapToGlobal(e->pos()); - - QString toRemove = _hrows->at(_sel); - RecentHashtagPack recent(cRecentWriteHashtags()); - for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { - if (i->first == toRemove) { - i = recent.erase(i); - } else { - ++i; + bool removed = false; + if (_mrows->isEmpty()) { + QString toRemove = _hrows->at(_sel); + RecentHashtagPack &recent(cRefRecentWriteHashtags()); + for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { + if (i->first == toRemove) { + i = recent.erase(i); + removed = true; + } else { + ++i; + } + } + } else { + UserData *toRemove = _mrows->at(_sel); + RecentInlineBots &recent(cRefRecentInlineBots()); + int32 index = recent.indexOf(toRemove); + if (index >= 0) { + recent.remove(index); + removed = true; } } - cSetRecentWriteHashtags(recent); - Local::writeRecentHashtags(); + if (removed) { + Local::writeRecentHashtagsAndBots(); + } _parent->updateFiltered(); _mouseSel = true; @@ -3980,8 +3996,8 @@ void MentionsInner::onUpdateSelected(bool force) { if ((!force && !rect().contains(mouse)) || !_mouseSel) return; int w = width(), mouseY = mouse.y(); - _overDelete = _mrows->isEmpty() && (mouse.x() >= w - st::mentionHeight); int32 sel = mouseY / int32(st::mentionHeight), maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); + _overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= w - st::mentionHeight) : false; if (sel < 0 || sel >= maxSel) { sel = -1; } @@ -4043,15 +4059,23 @@ void MentionsDropdown::paintEvent(QPaintEvent *e) { } -void MentionsDropdown::showFiltered(PeerData *peer, QString start) { +void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) { + if (query.isEmpty() || (peer->isUser() && query.at(0) == '@' && (!start || cRecentInlineBots().isEmpty()))) { + if (!isHidden()) { + hideStart(); + } + return; + } + _chat = peer->asChat(); _user = peer->asUser(); _channel = peer->asChannel(); - start = start.toLower(); - bool toDown = (_filter != start); + query = query.toLower(); + bool toDown = (_filter != query); if (toDown) { - _filter = start; + _filter = query; } + _addInlineBots = start; updateFiltered(toDown); } @@ -4063,13 +4087,34 @@ bool MentionsDropdown::clearFilteredBotCommands() { } void MentionsDropdown::updateFiltered(bool toDown) { - int32 now = unixtime(); - MentionRows rows; + int32 now = unixtime(), recentInlineBots = 0; + MentionRows mrows; HashtagRows hrows; BotCommandRows brows; + if (_filter.at(0) == '@') { + if (_chat) { + mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); + } else if (_channel && _channel->isMegagroup()) { + if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { + } else { + mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + _channel->mgInfo->lastParticipants.size()); + } + } else if (_addInlineBots) { + mrows.reserve(cRecentInlineBots().size()); + } + if (_addInlineBots) { + for (RecentInlineBots::const_iterator i = cRecentInlineBots().cbegin(), e = cRecentInlineBots().cend(); i != e; ++i) { + UserData *user = *i; + if (user->username.isEmpty()) continue; + if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + mrows.push_back(user); + ++recentInlineBots; + } + } + } if (_filter.at(0) == '@' && _chat) { QMultiMap ordered; - rows.reserve(_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()); + mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); if (_chat->noParticipantInfo()) { if (App::api()) App::api()->requestFullPeer(_chat); } else if (!_chat->participants.isEmpty()) { @@ -4084,7 +4129,7 @@ void MentionsDropdown::updateFiltered(bool toDown) { UserData *user = *i; if (user->username.isEmpty()) continue; if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - rows.push_back(user); + mrows.push_back(user); if (!ordered.isEmpty()) { ordered.remove(App::onlineForSort(user, now), user); } @@ -4092,7 +4137,7 @@ void MentionsDropdown::updateFiltered(bool toDown) { if (!ordered.isEmpty()) { for (QMultiMap::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) { --i; - rows.push_back(i.value()); + mrows.push_back(i.value()); } } } else if (_filter.at(0) == '@' && _channel && _channel->isMegagroup()) { @@ -4100,12 +4145,12 @@ void MentionsDropdown::updateFiltered(bool toDown) { if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { if (App::api()) App::api()->requestLastParticipants(_channel); } else { - rows.reserve(_channel->mgInfo->lastParticipants.size()); + mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) { UserData *user = *i; if (user->username.isEmpty()) continue; if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - rows.push_back(user); + mrows.push_back(user); } } } else if (_filter.at(0) == '#') { @@ -4184,7 +4229,8 @@ void MentionsDropdown::updateFiltered(bool toDown) { } } } - rowsUpdated(rows, hrows, brows, toDown); + rowsUpdated(mrows, hrows, brows, toDown); + _inner.setRecentInlineBotsInRows(recentInlineBots); } void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown) { diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index b86a1d6f2..75ce7c634 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -736,6 +736,8 @@ public: bool moveSel(int direction); bool select(); + void setRecentInlineBotsInRows(int32 bots); + QString getSelected() const; signals: @@ -756,6 +758,7 @@ private: MentionRows *_mrows; HashtagRows *_hrows; BotCommandRows *_brows; + int32 _recentInlineBotsInRows; int32 _sel; bool _mouseSel; QPoint _mousePos; @@ -775,7 +778,7 @@ public: void fastHide(); bool clearFilteredBotCommands(); - void showFiltered(PeerData *peer, QString start); + void showFiltered(PeerData *peer, QString query, bool start); void updateFiltered(bool toDown = false); void setBoundings(QRect boundings); @@ -820,7 +823,7 @@ private: HashtagRows _hrows; BotCommandRows _brows; - void rowsUpdated(const MentionRows &rows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown); + void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown); ScrollArea _scroll; MentionsInner _inner; @@ -830,6 +833,7 @@ private: ChannelData *_channel; QString _filter; QRect _boundings; + bool _addInlineBots; int32 _width, _height; bool _hiding; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 2c5db2dfd..09b132740 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -129,7 +129,7 @@ namespace Ui { void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) { if (MainWidget *m = App::main()) { - QMetaObject::invokeMethod(m, SLOT(ui_showPeerHistoryAsync(quint64,qint32)), Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId)); + QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId)); } } diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index ae09ce169..4b88c0110 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -270,7 +270,9 @@ EmojiPtr FlatTextarea::getSingleEmoji() const { return 0; } -void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&inlineBot, QString &inlineBotUsername) const { +QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start, UserData *&inlineBot, QString &inlineBotUsername) const { + start = false; + // check inline bot query const QString &text(getLastText()); int32 inlineUsernameStart = 1, inlineUsernameLength = 0, size = text.size(); @@ -301,13 +303,12 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i inlineBot = InlineBotLookingUpData; } } - if (inlineBot == InlineBotLookingUpData) return; + if (inlineBot == InlineBotLookingUpData) return QString(); if (inlineBot && (!inlineBot->botInfo || inlineBot->botInfo->inlinePlaceholder.isEmpty())) { inlineBot = 0; } else { - start = text.mid(inlineUsernameStart + inlineUsernameLength + 1); - return; + return text.mid(inlineUsernameStart + inlineUsernameLength + 1); } } else { inlineUsernameLength = 0; @@ -319,7 +320,7 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i } int32 pos = textCursor().position(); - if (textCursor().anchor() != pos) return; + if (textCursor().anchor() != pos) return QString(); // check mention / hashtag / bot command QTextDocument *doc(document()); @@ -339,29 +340,33 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i for (int i = pos - p; i > 0; --i) { if (t.at(i - 1) == '@') { if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) { mentionInCommand = true; --i; continue; } - return; + return QString(); } else if (t.at(i - 1) == '#') { if (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_')) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } - return; + return QString(); } else if (t.at(i - 1) == '/') { if (i < 2) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } - return; + return QString(); } if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break; if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break; } - return; + break; } + return QString(); } void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) { diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h index 8020a8d95..c7e74efde 100644 --- a/Telegram/SourceFiles/gui/flattextarea.h +++ b/Telegram/SourceFiles/gui/flattextarea.h @@ -64,7 +64,7 @@ public: QSize minimumSizeHint() const; EmojiPtr getSingleEmoji() const; - void getMentionHashtagBotCommandStart(QString &start, UserData *&contextBot, QString &contextBotUsername) const; + QString getMentionHashtagBotCommandPart(bool &start, UserData *&contextBot, QString &contextBotUsername) const; void removeSingleEmoji(); bool hasText() const; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index e11484216..a373a9413 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5338,8 +5338,9 @@ void HistoryWidget::onCheckMentionDropdown() { if (!_history || _a_show.animating()) return; UserData *bot = _inlineBot; - QString start, inlineBotUsername(_inlineBotUsername); - _field.getMentionHashtagBotCommandStart(start, _inlineBot, _inlineBotUsername); + bool start = false; + QString inlineBotUsername(_inlineBotUsername); + QString query = _field.getMentionHashtagBotCommandPart(start, _inlineBot, _inlineBotUsername); if (inlineBotUsername != _inlineBotUsername) { if (_inlineBotResolveRequestId) { Notify::inlineBotRequesting(false); @@ -5359,10 +5360,10 @@ void HistoryWidget::onCheckMentionDropdown() { if (_inlineBot != bot) { updateFieldPlaceholder(); } - if (_inlineBot->username == (cTestMode() ? qstr("contextbot") : qstr("gif")) && start.isEmpty()) { + if (_inlineBot->username == (cTestMode() ? qstr("contextbot") : qstr("gif")) && query.isEmpty()) { _emojiPan.clearInlineBot(); } else { - _emojiPan.queryInlineBot(_inlineBot, start); + _emojiPan.queryInlineBot(_inlineBot, query); } if (!_attachMention.isHidden()) { _attachMention.hideStart(); @@ -5373,16 +5374,12 @@ void HistoryWidget::onCheckMentionDropdown() { _field.finishPlaceholder(); } _emojiPan.clearInlineBot(); - if (!start.isEmpty()) { - if (start.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtags(); - if (start.at(0) == '@' && _peer->isUser()) return; - if (start.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return; - _attachMention.showFiltered(_peer, start); - } else { - if (!_attachMention.isHidden()) { - _attachMention.hideStart(); - } + if (!query.isEmpty()) { + if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots(); + if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots(); + if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return; } + _attachMention.showFiltered(_peer, query, start); } } @@ -6428,6 +6425,18 @@ void HistoryWidget::onInlineResultSend(InlineResult *result, UserData *bot) { _saveDraftStart = getms(); onDraftSave(); + RecentInlineBots &bots(cRefRecentInlineBots()); + int32 index = bots.indexOf(bot); + if (index) { + if (index > 0) { + bots.removeAt(index); + } else if (bots.size() >= RecentInlineBotsLimit) { + bots.resize(RecentInlineBotsLimit - 1); + } + bots.push_front(bot); + Local::writeRecentHashtagsAndBots(); + } + onCheckMentionDropdown(); if (!_attachType.isHidden()) _attachType.hideStart(); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index d7dfc9d9e..d6deee939 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -538,22 +538,22 @@ namespace { FileKey _dataNameKey = 0; enum { // Local Storage Keys - lskUserMap = 0x00, - lskDraft = 0x01, // data: PeerId peer - lskDraftPosition = 0x02, // data: PeerId peer - lskImages = 0x03, // data: StorageKey location - lskLocations = 0x04, // no data - lskStickerImages = 0x05, // data: StorageKey location - lskAudios = 0x06, // data: StorageKey location - lskRecentStickersOld = 0x07, // no data - lskBackground = 0x08, // no data - lskUserSettings = 0x09, // no data - lskRecentHashtags = 0x0a, // no data - lskStickers = 0x0b, // no data - lskSavedPeers = 0x0c, // no data - lskReportSpamStatuses = 0x0d, // no data - lskSavedGifsOld = 0x0e, - lskSavedGifs = 0x0f, + lskUserMap = 0x00, + lskDraft = 0x01, // data: PeerId peer + lskDraftPosition = 0x02, // data: PeerId peer + lskImages = 0x03, // data: StorageKey location + lskLocations = 0x04, // no data + lskStickerImages = 0x05, // data: StorageKey location + lskAudios = 0x06, // data: StorageKey location + lskRecentStickersOld = 0x07, // no data + lskBackground = 0x08, // no data + lskUserSettings = 0x09, // no data + lskRecentHashtagsAndBots = 0x0a, // no data + lskStickers = 0x0b, // no data + lskSavedPeers = 0x0c, // no data + lskReportSpamStatuses = 0x0d, // no data + lskSavedGifsOld = 0x0e, // no data + lskSavedGifs = 0x0f, // no data }; typedef QMap DraftsMap; @@ -581,8 +581,8 @@ namespace { bool _backgroundWasRead = false; FileKey _userSettingsKey = 0; - FileKey _recentHashtagsKey = 0; - bool _recentHashtagsWereRead = false; + FileKey _recentHashtagsAndBotsKey = 0; + bool _recentHashtagsAndBotsWereRead = false; FileKey _savedPeersKey = 0; @@ -726,18 +726,20 @@ namespace { _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); } - _storageWebFilesSize = 0; - _webFilesMap.clear(); + if (!locations.stream.atEnd()) { + _storageWebFilesSize = 0; + _webFilesMap.clear(); - quint32 webLocationsCount; - locations.stream >> webLocationsCount; - for (quint32 i = 0; i < webLocationsCount; ++i) { - QString url; - quint64 key; - qint32 size; - locations.stream >> url >> key >> size; - _webFilesMap.insert(url, FileDesc(key, size)); - _storageWebFilesSize += size; + quint32 webLocationsCount; + locations.stream >> webLocationsCount; + for (quint32 i = 0; i < webLocationsCount; ++i) { + QString url; + quint64 key; + qint32 size; + locations.stream >> url >> key >> size; + _webFilesMap.insert(url, FileDesc(key, size)); + _storageWebFilesSize += size; + } } } } @@ -1667,7 +1669,7 @@ namespace { qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; quint64 locationsKey = 0, reportSpamStatusesKey = 0; quint64 recentStickersKeyOld = 0, stickersKey = 0, savedGifsKey = 0; - quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0, savedPeersKey = 0; + quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0; while (!map.stream.atEnd()) { quint32 keyType; map.stream >> keyType; @@ -1744,8 +1746,8 @@ namespace { case lskUserSettings: { map.stream >> userSettingsKey; } break; - case lskRecentHashtags: { - map.stream >> recentHashtagsKey; + case lskRecentHashtagsAndBots: { + map.stream >> recentHashtagsAndBotsKey; } break; case lskStickers: { map.stream >> stickersKey; @@ -1788,7 +1790,7 @@ namespace { _savedPeersKey = savedPeersKey; _backgroundKey = backgroundKey; _userSettingsKey = userSettingsKey; - _recentHashtagsKey = recentHashtagsKey; + _recentHashtagsAndBotsKey = recentHashtagsAndBotsKey; _oldMapVersion = mapData.version; if (_oldMapVersion < AppVersion) { _mapChanged = true; @@ -1861,7 +1863,7 @@ namespace { if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_recentHashtagsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); EncryptedDescriptor mapData(mapSize); if (!_draftsMap.isEmpty()) { mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size()); @@ -1917,8 +1919,8 @@ namespace { if (_userSettingsKey) { mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey); } - if (_recentHashtagsKey) { - mapData.stream << quint32(lskRecentHashtags) << quint64(_recentHashtagsKey); + if (_recentHashtagsAndBotsKey) { + mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey); } map.writeEncrypted(mapData); @@ -2187,7 +2189,7 @@ namespace Local { _storageWebFilesSize = 0; _locationsKey = _reportSpamStatusesKey = 0; _recentStickersKeyOld = _stickersKey = _savedGifsKey = 0; - _backgroundKey = _userSettingsKey = _recentHashtagsKey = _savedPeersKey = 0; + _backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0; _oldMapVersion = _oldSettingsVersion = 0; _mapChanged = true; _writeMap(WriteMapNow); @@ -3258,89 +3260,6 @@ namespace Local { return false; } - void writeRecentHashtags() { - if (!_working()) return; - - const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); - if (write.isEmpty() && search.isEmpty()) readRecentHashtags(); - if (write.isEmpty() && search.isEmpty()) { - if (_recentHashtagsKey) { - clearKey(_recentHashtagsKey); - _recentHashtagsKey = 0; - _mapChanged = true; - } - _writeMap(); - } else { - if (!_recentHashtagsKey) { - _recentHashtagsKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - quint32 size = sizeof(quint32) * 2, writeCnt = 0, searchCnt = 0; - for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) { - if (!i->first.isEmpty()) { - size += _stringSize(i->first) + sizeof(quint16); - ++writeCnt; - } - } - for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) { - if (!i->first.isEmpty()) { - size += _stringSize(i->first) + sizeof(quint16); - ++searchCnt; - } - } - EncryptedDescriptor data(size); - data.stream << quint32(writeCnt) << quint32(searchCnt); - for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - FileWriteDescriptor file(_recentHashtagsKey); - file.writeEncrypted(data); - } - } - - void readRecentHashtags() { - if (_recentHashtagsWereRead) return; - _recentHashtagsWereRead = true; - - if (!_recentHashtagsKey) return; - - FileReadDescriptor hashtags; - if (!readEncryptedFile(hashtags, _recentHashtagsKey)) { - clearKey(_recentHashtagsKey); - _recentHashtagsKey = 0; - _writeMap(); - return; - } - - quint32 writeCount = 0, searchCount = 0; - hashtags.stream >> writeCount >> searchCount; - - QString tag; - quint16 count; - - RecentHashtagPack write, search; - if (writeCount) { - write.reserve(writeCount); - for (uint32 i = 0; i < writeCount; ++i) { - hashtags.stream >> tag >> count; - write.push_back(qMakePair(tag.trimmed(), count)); - } - } - if (searchCount) { - search.reserve(searchCount); - for (uint32 i = 0; i < searchCount; ++i) { - hashtags.stream >> tag >> count; - search.push_back(qMakePair(tag.trimmed(), count)); - } - } - cSetRecentWriteHashtags(write); - cSetRecentSearchHashtags(search); - } - uint32 _peerSize(PeerData *peer) { uint32 result = sizeof(quint64) + sizeof(quint64) + _storageImageLocationSize(); if (peer->isUser()) { @@ -3370,7 +3289,7 @@ namespace Local { return result; } - void _writePeer(QDataStream &stream, PeerData *peer) { + void _writePeer(QDataStream &stream, PeerData *peer, int32 fileVersion = AppVersion) { stream << quint64(peer->id) << quint64(peer->photoId); _writeStorageImageLocation(stream, peer->photoLoc); if (peer->isUser()) { @@ -3380,6 +3299,9 @@ namespace Local { if (AppVersion >= 9012) { stream << qint32(user->flags); } + if (AppVersion >= 9016 || fileVersion >= 9016) { + stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString()); + } stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1); } else if (peer->isChat()) { ChatData *chat = peer->asChat(); @@ -3396,25 +3318,31 @@ namespace Local { } } - PeerData *_readPeer(FileReadDescriptor &from) { + PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { PeerData *result = 0; quint64 peerId = 0, photoId = 0; from.stream >> peerId >> photoId; StorageImageLocation photoLoc(_readStorageImageLocation(from)); + result = App::peerLoaded(peerId); + if (result && result->loaded) return result; + result = App::peer(peerId); result->loaded = true; if (result->isUser()) { UserData *user = result->asUser(); - QString first, last, phone, username; + QString first, last, phone, username, inlinePlaceholder; quint64 access; qint32 flags = 0, onlineTill, contact, botInfoVersion; from.stream >> first >> last >> phone >> username >> access; if (from.version >= 9012) { from.stream >> flags; } + if (from.version >= 9016 || fileVersion >= 9016) { + from.stream >> inlinePlaceholder; + } from.stream >> onlineTill >> contact >> botInfoVersion; bool showPhone = !isServiceUser(user->id) && (peerToUser(user->id) != MTP::authedId()) && (contact <= 0); @@ -3427,6 +3355,9 @@ namespace Local { user->onlineTill = onlineTill; user->contact = contact; user->setBotInfoVersion(botInfoVersion); + if (!inlinePlaceholder.isEmpty() && user->botInfo) { + user->botInfo->inlinePlaceholder = inlinePlaceholder; + } if (peerToUser(user->id) == MTP::authedId()) { user->input = MTP_inputPeerSelf(); @@ -3489,6 +3420,113 @@ namespace Local { return result; } + void writeRecentHashtagsAndBots() { + if (!_working()) return; + + const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); + const RecentInlineBots &bots(cRecentInlineBots()); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots(); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { + if (_recentHashtagsAndBotsKey) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + if (!_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size(); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += _stringSize(i->first) + sizeof(quint16); + ++writeCnt; + } + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += _stringSize(i->first) + sizeof(quint16); + ++searchCnt; + } + } + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + size += _peerSize(*i); + } + + EncryptedDescriptor data(size); + data.stream << quint32(writeCnt) << quint32(searchCnt); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + data.stream << quint32(botsCnt); + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + _writePeer(data.stream, *i, 9016); + } + FileWriteDescriptor file(_recentHashtagsAndBotsKey); + file.writeEncrypted(data); + } + } + + void readRecentHashtagsAndBots() { + if (_recentHashtagsAndBotsWereRead) return; + _recentHashtagsAndBotsWereRead = true; + + if (!_recentHashtagsAndBotsKey) return; + + FileReadDescriptor hashtags; + if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _writeMap(); + return; + } + + quint32 writeCount = 0, searchCount = 0, botsCount = 0; + hashtags.stream >> writeCount >> searchCount; + + QString tag; + quint16 count; + + RecentHashtagPack write, search; + RecentInlineBots bots; + if (writeCount) { + write.reserve(writeCount); + for (uint32 i = 0; i < writeCount; ++i) { + hashtags.stream >> tag >> count; + write.push_back(qMakePair(tag.trimmed(), count)); + } + } + if (searchCount) { + search.reserve(searchCount); + for (uint32 i = 0; i < searchCount; ++i) { + hashtags.stream >> tag >> count; + search.push_back(qMakePair(tag.trimmed(), count)); + } + } + cSetRecentWriteHashtags(write); + cSetRecentSearchHashtags(search); + + if (!hashtags.stream.atEnd()) { + hashtags.stream >> botsCount; + if (botsCount) { + bots.reserve(botsCount); + for (uint32 i = 0; i < botsCount; ++i) { + PeerData *peer = _readPeer(hashtags, 9016); + if (peer && peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) { + bots.push_back(peer->asUser()); + } + } + } + cSetRecentInlineBots(bots); + } + } + void writeSavedPeers() { if (!_working()) return; @@ -3651,8 +3689,8 @@ namespace Local { _stickersKey = 0; _mapChanged = true; } - if (_recentHashtagsKey) { - _recentHashtagsKey = 0; + if (_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = 0; _mapChanged = true; } if (_savedPeersKey) { diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index aa4f51432..6bf8e7c9d 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -157,8 +157,8 @@ namespace Local { void writeBackground(int32 id, const QImage &img); bool readBackground(); - void writeRecentHashtags(); - void readRecentHashtags(); + void writeRecentHashtagsAndBots(); + void readRecentHashtagsAndBots(); void addSavedPeer(PeerData *peer, const QDateTime &position); void removeSavedPeer(PeerData *peer); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index ba51e53d2..e73ed08df 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1349,7 +1349,7 @@ void MainWidget::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); recent = cRecentWriteHashtags(); } found = true; @@ -1357,7 +1357,7 @@ void MainWidget::saveRecentHashtags(const QString &text) { } if (found) { cSetRecentWriteHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); } } diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 6fa99541b..8b2700c4e 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -121,6 +121,8 @@ int32 gSavedGifsLimit = 100; RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags; +RecentInlineBots gRecentInlineBots; + bool gPasswordRecovered = false; int32 gPasscodeBadTries = 0; uint64 gPasscodeLastTry = 0; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index a5aac46f1..25b34f573 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -230,9 +230,13 @@ DeclareSetting(bool, ShowingSavedGifs); DeclareSetting(int32, SavedGifsLimit); typedef QList > RecentHashtagPack; -DeclareSetting(RecentHashtagPack, RecentWriteHashtags); +DeclareRefSetting(RecentHashtagPack, RecentWriteHashtags); DeclareSetting(RecentHashtagPack, RecentSearchHashtags); +class UserData; +typedef QVector RecentInlineBots; +DeclareRefSetting(RecentInlineBots, RecentInlineBots); + DeclareSetting(bool, PasswordRecovered); DeclareSetting(int32, PasscodeBadTries); diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index af8a70d02..630f3982e 100644 --- a/Telegram/Telegram.rc +++ b/Telegram/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "SourceFiles\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,15,5 - PRODUCTVERSION 0,9,15,5 + FILEVERSION 0,9,15,6 + PRODUCTVERSION 0,9,15,6 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.15.5" + VALUE "FileVersion", "0.9.15.6" VALUE "LegalCopyright", "Copyright (C) 2013" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.15.5" + VALUE "ProductVersion", "0.9.15.6" END END BLOCK "VarFileInfo"