mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-07-15 02:02:52 +02:00
feat: implement AyuForward
This commit is contained in:
parent
0cd2617576
commit
e097a5de58
29 changed files with 1084 additions and 18 deletions
|
@ -168,6 +168,10 @@ set(ayugram_files
|
|||
ayu/features/streamer_mode/streamer_mode.h
|
||||
ayu/features/messageshot/message_shot.cpp
|
||||
ayu/features/messageshot/message_shot.h
|
||||
ayu/features/forward/ayu_forward.cpp
|
||||
ayu/features/forward/ayu_forward.h
|
||||
ayu/features/forward/ayu_sync.cpp
|
||||
ayu/features/forward/ayu_sync.h
|
||||
ayu/data/messages_storage.cpp
|
||||
ayu/data/messages_storage.h
|
||||
ayu/data/entities.h
|
||||
|
|
|
@ -90,6 +90,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/ayu_worker.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "ayu/features/forward/ayu_forward.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
@ -3279,6 +3280,22 @@ void ApiWrap::forwardMessages(
|
|||
FnMut<void()> &&successCallback) {
|
||||
Expects(!draft.items.empty());
|
||||
|
||||
const auto fullAyuForward = AyuForward::isFullAyuForwardNeeded(draft.items.front());
|
||||
if (fullAyuForward) {
|
||||
crl::async([=] {
|
||||
AyuForward::forwardMessages(_session, action, false, draft);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ayuIntelligentForwardNeeded = AyuForward::isAyuForwardNeeded(draft.items);
|
||||
if (ayuIntelligentForwardNeeded) {
|
||||
crl::async([=] {
|
||||
AyuForward::intelligentForward(_session, action, draft);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
auto &histories = _session->data().histories();
|
||||
|
||||
struct SharedCallback {
|
||||
|
@ -3766,8 +3783,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
|||
? replyTo->topicRootId()
|
||||
: Data::ForumTopic::kGeneralId;
|
||||
const auto topic = peer->forumTopicFor(topicRootId);
|
||||
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|
||||
|| Api::SendDice(message)) {
|
||||
|
||||
const bool canSendTexts = topic
|
||||
? Data::CanSendTexts(topic)
|
||||
: Data::CanSendTexts(peer);
|
||||
|
||||
if (!canSendTexts && !AyuForward::isForwarding(peer->id) || Api::SendDice(message)) {
|
||||
return;
|
||||
}
|
||||
local().saveRecentSentHashtags(textWithTags.text);
|
||||
|
|
402
Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp
Normal file
402
Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp
Normal file
|
@ -0,0 +1,402 @@
|
|||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2025
|
||||
#include "ayu_forward.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ayu_sync.h"
|
||||
#include "lang_auto.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history_item.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
namespace AyuForward {
|
||||
|
||||
std::unordered_map<PeerId, std::shared_ptr<ForwardState>> forwardStates;
|
||||
|
||||
bool isForwarding(const PeerId &id) {
|
||||
const auto fwState = forwardStates.find(id);
|
||||
if (id.value && fwState != forwardStates.end()) {
|
||||
const auto state = *fwState->second;
|
||||
|
||||
return state.state != ForwardState::State::Finished
|
||||
&& state.currentChunk < state.totalChunks
|
||||
&& !state.stopRequested
|
||||
&& state.totalChunks
|
||||
&& state.totalMessages;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cancelForward(const PeerId &id, const Main::Session &session) {
|
||||
const auto fwState = forwardStates.find(id);
|
||||
if (fwState != forwardStates.end()) {
|
||||
fwState->second->stopRequested = true;
|
||||
fwState->second->updateBottomBar(session, &id, ForwardState::State::Finished);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<QString, QString> stateName(const PeerId &id) {
|
||||
const auto fwState = forwardStates.find(id);
|
||||
|
||||
|
||||
if (fwState == forwardStates.end()) {
|
||||
return std::make_pair(QString(), QString());
|
||||
}
|
||||
|
||||
const auto state = fwState->second;
|
||||
|
||||
QString messagesString = tr::ayu_AyuForwardStatusSentCount(tr::now,
|
||||
lt_count1,
|
||||
QString::number(state->sentMessages),
|
||||
lt_count2,
|
||||
QString::number(state->totalMessages)
|
||||
|
||||
);
|
||||
|
||||
QString chunkString = tr::ayu_AyuForwardStatusChunkCount(tr::now,
|
||||
lt_count1,
|
||||
QString::number(state->currentChunk + 1),
|
||||
lt_count2,
|
||||
QString::number(state->totalChunks)
|
||||
|
||||
);
|
||||
|
||||
const auto partString = state->totalChunks <= 1 ? messagesString : (messagesString + " • " + chunkString);
|
||||
|
||||
QString status;
|
||||
|
||||
if (state->state == ForwardState::State::Preparing) {
|
||||
status = tr::ayu_AyuForwardStatusPreparing(tr::now);
|
||||
} else if (state->state == ForwardState::State::Downloading) {
|
||||
status = tr::ayu_AyuForwardStatusLoadingMedia(tr::now);
|
||||
} else if (state->state == ForwardState::State::Sending) {
|
||||
status = tr::ayu_AyuForwardStatusForwarding(tr::now);
|
||||
} else {
|
||||
// ForwardState::State::Finished
|
||||
status = tr::ayu_AyuForwardStatusFinished(tr::now);
|
||||
}
|
||||
|
||||
|
||||
return std::make_pair(status, partString);
|
||||
}
|
||||
|
||||
void ForwardState::updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st) {
|
||||
state = st;
|
||||
|
||||
session.changes().peerUpdated(session.data().peer(*peer), Data::PeerUpdate::Flag::Rights);
|
||||
}
|
||||
|
||||
static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
|
||||
const std::vector<not_null<HistoryItem*>> &items,
|
||||
int &i,
|
||||
std::vector<not_null<Data::Media*>> &groupMedia) {
|
||||
const auto prepare = [&](not_null<Data::Media*> media)
|
||||
{
|
||||
groupMedia.emplace_back(media);
|
||||
auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media));
|
||||
Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, PhotoSideLimit());
|
||||
return prepared;
|
||||
};
|
||||
|
||||
const auto startItem = items[i];
|
||||
const auto media = startItem->media();
|
||||
const auto groupId = startItem->groupId();
|
||||
|
||||
Ui::PreparedList list;
|
||||
list.files.emplace_back(prepare(media));
|
||||
|
||||
if (!groupId.value) {
|
||||
return list;
|
||||
}
|
||||
|
||||
for (int k = i + 1; k < items.size(); ++k) {
|
||||
const auto nextItem = items[k];
|
||||
if (nextItem->groupId() != groupId) {
|
||||
break;
|
||||
}
|
||||
if (const auto nextMedia = nextItem->media()) {
|
||||
list.files.emplace_back(prepare(nextMedia));
|
||||
i = k;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void sendMedia(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Ui::PreparedBundle> bundle,
|
||||
not_null<Data::Media*> primaryMedia,
|
||||
Api::MessageToSend &&message,
|
||||
bool sendImagesAsPhotos) {
|
||||
if (const auto document = primaryMedia->document(); document && document->sticker()) {
|
||||
AyuSync::sendStickerSync(session, message, document);
|
||||
return;
|
||||
}
|
||||
|
||||
auto mediaType = [&]
|
||||
{
|
||||
if (const auto document = primaryMedia->document()) {
|
||||
if (document->isVoiceMessage()) {
|
||||
return SendMediaType::Audio;
|
||||
} else if (document->isVideoMessage()) {
|
||||
return SendMediaType::Round;
|
||||
}
|
||||
return SendMediaType::File;
|
||||
}
|
||||
return SendMediaType::Photo;
|
||||
}();
|
||||
|
||||
if (mediaType == SendMediaType::Round || mediaType == SendMediaType::Audio) {
|
||||
const auto path = bundle->groups.front().list.files.front().path;
|
||||
|
||||
QFile file(path);
|
||||
auto failed = false;
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
LOG(("failed to open file for forward with reason: %1").arg(file.errorString()));
|
||||
failed = true;
|
||||
}
|
||||
auto data = file.readAll();
|
||||
|
||||
if (!failed && data.size()) {
|
||||
file.close();
|
||||
AyuSync::sendVoiceSync(session,
|
||||
data,
|
||||
primaryMedia->document()->duration(),
|
||||
mediaType == SendMediaType::Round,
|
||||
message.action);
|
||||
return;
|
||||
}
|
||||
// at least try to send it as squared-video
|
||||
}
|
||||
|
||||
// workaround for media albums consisting of video and photos
|
||||
if (sendImagesAsPhotos) {
|
||||
mediaType = SendMediaType::Photo;
|
||||
}
|
||||
|
||||
for (auto &group : bundle->groups) {
|
||||
AyuSync::sendDocumentSync(
|
||||
session,
|
||||
group,
|
||||
mediaType,
|
||||
std::move(message.textWithTags),
|
||||
message.action);
|
||||
}
|
||||
}
|
||||
|
||||
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem*>> &items) {
|
||||
for (const auto &item : items) {
|
||||
if (isAyuForwardNeeded(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAyuForwardNeeded(not_null<HistoryItem*> item) {
|
||||
if (item->isDeleted() || item->isAyuNoForwards() || item->ttlDestroyAt()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isFullAyuForwardNeeded(not_null<HistoryItem*> item) {
|
||||
return item->from()->isAyuNoForwards() || item->history()->peer->isAyuNoForwards();
|
||||
}
|
||||
|
||||
struct ForwardChunk
|
||||
{
|
||||
bool isAyuForwardNeeded;
|
||||
std::vector<not_null<HistoryItem*>> items;
|
||||
};
|
||||
|
||||
void intelligentForward(
|
||||
not_null<Main::Session*> session,
|
||||
const Api::SendAction &action,
|
||||
Data::ResolvedForwardDraft draft) {
|
||||
const auto history = action.history;
|
||||
history->setForwardDraft(action.replyTo.topicRootId, {});
|
||||
|
||||
const auto items = draft.items;
|
||||
const auto peer = history->peer;
|
||||
|
||||
auto chunks = std::vector<ForwardChunk>();
|
||||
auto currentArray = std::vector<not_null<HistoryItem*>>();
|
||||
|
||||
auto currentChunk = ForwardChunk({
|
||||
.isAyuForwardNeeded = isAyuForwardNeeded(items[0]),
|
||||
.items = currentArray
|
||||
});
|
||||
|
||||
for (const auto &item : items) {
|
||||
if (isAyuForwardNeeded(item) != currentChunk.isAyuForwardNeeded) {
|
||||
currentChunk.items = currentArray;
|
||||
chunks.push_back(currentChunk);
|
||||
|
||||
currentArray = std::vector<not_null<HistoryItem*>>();
|
||||
|
||||
currentChunk = ForwardChunk({
|
||||
.isAyuForwardNeeded = isAyuForwardNeeded(item),
|
||||
.items = currentArray
|
||||
});
|
||||
}
|
||||
currentArray.push_back(item);
|
||||
}
|
||||
|
||||
currentChunk.items = currentArray;
|
||||
chunks.push_back(currentChunk);
|
||||
|
||||
auto state = std::make_shared<ForwardState>(chunks.size());
|
||||
forwardStates[peer->id] = state;
|
||||
|
||||
|
||||
for (const auto &chunk : chunks) {
|
||||
if (chunk.isAyuForwardNeeded) {
|
||||
forwardMessages(session, action, true, Data::ResolvedForwardDraft(chunk.items));
|
||||
} else {
|
||||
state->totalMessages = chunk.items.size();
|
||||
state->sentMessages = 0;
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
|
||||
|
||||
AyuSync::forwardMessagesSync(session, chunk.items, action, draft.options);
|
||||
|
||||
state->sentMessages = state->totalMessages;
|
||||
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
|
||||
}
|
||||
state->currentChunk++;
|
||||
}
|
||||
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
|
||||
}
|
||||
|
||||
void forwardMessages(
|
||||
not_null<Main::Session*> session,
|
||||
const Api::SendAction &action,
|
||||
bool forwardState,
|
||||
Data::ResolvedForwardDraft draft) {
|
||||
const auto items = draft.items;
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
|
||||
history->setForwardDraft(action.replyTo.topicRootId, {});
|
||||
|
||||
std::shared_ptr<ForwardState> state;
|
||||
|
||||
if (forwardState) {
|
||||
state = std::make_shared<ForwardState>(*forwardStates[peer->id]);
|
||||
} else {
|
||||
state = std::make_shared<ForwardState>(1);
|
||||
}
|
||||
|
||||
forwardStates[peer->id] = state;
|
||||
|
||||
std::unordered_map<uint64, uint64> groupIds;
|
||||
|
||||
std::vector<not_null<HistoryItem*>> toBeDownloaded;
|
||||
|
||||
|
||||
for (const auto item : items) {
|
||||
if (mediaDownloadable(item->media())) {
|
||||
toBeDownloaded.push_back(item);
|
||||
}
|
||||
|
||||
if (item->groupId()) {
|
||||
const auto currentId = groupIds.find(item->groupId().value);
|
||||
|
||||
if (currentId == groupIds.end()) {
|
||||
groupIds[item->groupId().value] = base::RandomValue<uint64>();
|
||||
}
|
||||
}
|
||||
}
|
||||
state->totalMessages = items.size();
|
||||
if (!toBeDownloaded.empty()) {
|
||||
state->state = ForwardState::State::Downloading;
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Downloading);
|
||||
AyuSync::loadDocuments(session, toBeDownloaded);
|
||||
}
|
||||
|
||||
|
||||
state->sentMessages = 0;
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
const auto item = items[i];
|
||||
|
||||
if (state->stopRequested) {
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
|
||||
return;
|
||||
}
|
||||
|
||||
auto extractedText = extractText(item);
|
||||
if (extractedText.empty() && !mediaDownloadable(item->media())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto message = Api::MessageToSend(Api::SendAction(session->data().history(peer->id)));
|
||||
message.action.replyTo = action.replyTo;
|
||||
|
||||
if (draft.options != Data::ForwardOptions::NoNamesAndCaptions) {
|
||||
message.textWithTags = extractedText;
|
||||
}
|
||||
|
||||
if (!mediaDownloadable(item->media())) {
|
||||
AyuSync::sendMessageSync(session, message);
|
||||
} else if (const auto media = item->media()) {
|
||||
if (media->poll()) {
|
||||
AyuSync::sendMessageSync(session, message);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<not_null<Data::Media*>> groupMedia;
|
||||
auto preparedMedia = prepareMedia(session, items, i, groupMedia);
|
||||
|
||||
Ui::SendFilesWay way;
|
||||
way.setGroupFiles(true);
|
||||
way.setSendImagesAsPhotos(false);
|
||||
for (const auto &media2 : groupMedia) {
|
||||
if (media2->photo()) {
|
||||
way.setSendImagesAsPhotos(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto groups = Ui::DivideByGroups(
|
||||
std::move(preparedMedia),
|
||||
way,
|
||||
peer->slowmodeApplied());
|
||||
|
||||
auto bundle = Ui::PrepareFilesBundle(
|
||||
std::move(groups),
|
||||
way,
|
||||
message.textWithTags,
|
||||
false);
|
||||
sendMedia(session, bundle, media, std::move(message), way.sendImagesAsPhotos());
|
||||
}
|
||||
// if there are grouped messages
|
||||
// "i" is incremented in prepareMedia
|
||||
|
||||
state->sentMessages = i + 1;
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
|
||||
}
|
||||
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
|
||||
}
|
||||
|
||||
} // namespace AyuFeatures::AyuForward
|
52
Telegram/SourceFiles/ayu/features/forward/ayu_forward.h
Normal file
52
Telegram/SourceFiles/ayu/features/forward/ayu_forward.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2025
|
||||
#pragma once
|
||||
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace AyuForward {
|
||||
bool isForwarding(const PeerId &id);
|
||||
void cancelForward(const PeerId &id, const Main::Session &session);
|
||||
std::pair<QString, QString> stateName(const PeerId &id);
|
||||
|
||||
class ForwardState
|
||||
{
|
||||
public:
|
||||
enum class State
|
||||
{
|
||||
Preparing,
|
||||
Downloading,
|
||||
Sending,
|
||||
Finished
|
||||
};
|
||||
void updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st);
|
||||
|
||||
int totalChunks;
|
||||
int currentChunk;
|
||||
int totalMessages;
|
||||
int sentMessages;
|
||||
|
||||
State state = State::Preparing;
|
||||
bool stopRequested = false;
|
||||
|
||||
};
|
||||
|
||||
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem*>> &items);
|
||||
bool isAyuForwardNeeded(not_null<HistoryItem*> item);
|
||||
bool isFullAyuForwardNeeded(not_null<HistoryItem*> item);
|
||||
void intelligentForward(
|
||||
not_null<Main::Session*> session,
|
||||
const Api::SendAction &action,
|
||||
Data::ResolvedForwardDraft draft);
|
||||
void forwardMessages(
|
||||
not_null<Main::Session*> session,
|
||||
const Api::SendAction &action,
|
||||
bool forwardState,
|
||||
Data::ResolvedForwardDraft draft);
|
||||
|
||||
}
|
326
Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp
Normal file
326
Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp
Normal file
|
@ -0,0 +1,326 @@
|
|||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2025
|
||||
#include "ayu_sync.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_sending.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/file_download_mtproto.h"
|
||||
#include "storage/localimageloader.h"
|
||||
|
||||
class TimedCountDownLatch
|
||||
{
|
||||
public:
|
||||
explicit TimedCountDownLatch(int count)
|
||||
: count_(count) {
|
||||
}
|
||||
|
||||
void countDown() {
|
||||
std::unique_lock lock(mutex_);
|
||||
if (count_ > 0) {
|
||||
count_--;
|
||||
}
|
||||
if (count_ == 0) {
|
||||
cv_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
bool await(std::chrono::milliseconds timeout) {
|
||||
std::unique_lock lock(mutex_);
|
||||
if (count_ == 0) {
|
||||
return true;
|
||||
}
|
||||
return cv_.wait_for(lock, timeout, [this] { return count_ == 0; });
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
int count_;
|
||||
};
|
||||
|
||||
namespace AyuSync {
|
||||
|
||||
QString pathForSave(not_null<Main::Session*> session) {
|
||||
const auto path = Core::App().settings().downloadPath();
|
||||
if (path.isEmpty()) {
|
||||
return File::DefaultDownloadPath(session);
|
||||
}
|
||||
if (path == FileDialog::Tmp()) {
|
||||
return session->local().tempDirectory();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
QString filePath(not_null<Main::Session*> session, const Data::Media *media) {
|
||||
if (const auto document = media->document()) {
|
||||
if (!document->filename().isEmpty()) {
|
||||
return pathForSave(session) + media->document()->filename();
|
||||
}
|
||||
if (const auto name = document->filepath(true); !name.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
if (document->isVoiceMessage()) {
|
||||
return pathForSave(session) + "audio_" + QString::number(document->getDC()) + "_" +
|
||||
QString::number(document->id) + ".ogg";
|
||||
}
|
||||
if (document->isVideoMessage()) {
|
||||
return pathForSave(session) + "round_" + QString::number(document->getDC()) + "_" +
|
||||
QString::number(document->id) + ".mp4";
|
||||
}
|
||||
} else if (const auto photo = media->photo()) {
|
||||
return pathForSave(session) + QString::number(photo->getDC()) + "_" + QString::number(photo->id) + ".jpg";
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
qint64 fileSize(not_null<HistoryItem*> item) {
|
||||
if (const auto path = filePath(&item->history()->session(), item->media()); !path.isEmpty()) {
|
||||
QFile file(path);
|
||||
if (file.exists()) {
|
||||
auto size = file.size();
|
||||
return size;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void loadDocuments(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items) {
|
||||
for (const auto &item : items) {
|
||||
if (const auto data = item->media()->document()) {
|
||||
const auto size = fileSize(item);
|
||||
|
||||
if (size == data->size) {
|
||||
continue;
|
||||
}
|
||||
if (size && size < data->size) {
|
||||
// in case there some unfinished file
|
||||
QFile file(filePath(session, item->media()));
|
||||
file.remove();
|
||||
}
|
||||
|
||||
loadDocumentSync(session, data, item);
|
||||
} else if (auto photo = item->media()->photo()) {
|
||||
if (fileSize(item) == photo->imageByteSize(Data::PhotoSize::Large)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
loadPhotoSync(session, std::pair(photo, item->fullId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadDocumentSync(not_null<Main::Session*> session, DocumentData *data, not_null<HistoryItem*> item) {
|
||||
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
|
||||
data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media()));
|
||||
|
||||
rpl::single() | rpl::then(
|
||||
session->downloaderTaskFinished()
|
||||
) | rpl::filter([&]
|
||||
{
|
||||
return data->status == FileDownloadFailed || fileSize(item) == data->size;
|
||||
}) | rpl::start_with_next([&]() mutable
|
||||
{
|
||||
latch->countDown();
|
||||
base::take(lifetime)->destroy();
|
||||
},
|
||||
*lifetime);
|
||||
|
||||
latch->await(std::chrono::minutes(5));
|
||||
}
|
||||
|
||||
void forwardMessagesSync(not_null<Main::Session*> session,
|
||||
const std::vector<not_null<HistoryItem*>> &items,
|
||||
const ApiWrap::SendAction &action,
|
||||
Data::ForwardOptions options) {
|
||||
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
||||
|
||||
crl::on_main([=, &latch]
|
||||
{
|
||||
session->api().forwardMessages(Data::ResolvedForwardDraft(items, options),
|
||||
action,
|
||||
[&]
|
||||
{
|
||||
latch->countDown();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
latch->await(std::chrono::minutes(1));
|
||||
}
|
||||
|
||||
void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<PhotoData*>, FullMsgId> &photo) {
|
||||
const auto folderPath = pathForSave(session);
|
||||
const auto downloadPath = folderPath.isEmpty() ? Core::App().settings().downloadPath() : folderPath;
|
||||
|
||||
const auto path = downloadPath.isEmpty()
|
||||
? File::DefaultDownloadPath(session)
|
||||
: downloadPath == FileDialog::Tmp()
|
||||
? session->local().tempDirectory()
|
||||
: downloadPath;
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!QDir().mkpath(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto view = photo.first->createMediaView();
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
view->wanted(Data::PhotoSize::Large, photo.second);
|
||||
|
||||
const auto finalCheck = [=]
|
||||
{
|
||||
return !photo.first->loading();
|
||||
};
|
||||
|
||||
const auto saveToFiles = [=]
|
||||
{
|
||||
QDir directory(path);
|
||||
const auto dir = directory.absolutePath();
|
||||
const auto nameBase = dir.endsWith('/') ? dir : dir + '/';
|
||||
const auto fullPath = nameBase + QString::number(photo.first->getDC()) + "_" + QString::number(photo.first->id)
|
||||
+ ".jpg";
|
||||
view->saveToFile(fullPath);
|
||||
};
|
||||
|
||||
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
|
||||
if (finalCheck()) {
|
||||
saveToFiles();
|
||||
} else {
|
||||
session->downloaderTaskFinished() | rpl::filter([&]
|
||||
{
|
||||
return finalCheck();
|
||||
}) | rpl::start_with_next([&]() mutable
|
||||
{
|
||||
saveToFiles();
|
||||
latch->countDown();
|
||||
base::take(lifetime)->destroy();
|
||||
},
|
||||
*lifetime);
|
||||
}
|
||||
|
||||
latch->await(std::chrono::minutes(5));
|
||||
}
|
||||
|
||||
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message) {
|
||||
crl::on_main([=, &message]
|
||||
{
|
||||
// we cannot send events to objects
|
||||
// owned by a different thread
|
||||
// because sendMessage updates UI too
|
||||
|
||||
session->api().sendMessage(std::move(message));
|
||||
});
|
||||
|
||||
|
||||
waitForMsgSync(session, message.action);
|
||||
}
|
||||
|
||||
void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action) {
|
||||
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
|
||||
|
||||
session->data().itemIdChanged()
|
||||
| rpl::filter([&](const Data::Session::IdChange &update)
|
||||
{
|
||||
return action.history->peer->id == update.newId.peer;
|
||||
}) | rpl::start_with_next([&]
|
||||
{
|
||||
latch->countDown();
|
||||
base::take(lifetime)->destroy();
|
||||
},
|
||||
*lifetime);
|
||||
|
||||
latch->await(std::chrono::minutes(2));
|
||||
}
|
||||
|
||||
void sendDocumentSync(not_null<Main::Session*> session,
|
||||
Ui::PreparedGroup &group,
|
||||
SendMediaType type,
|
||||
TextWithTags &&caption,
|
||||
const Api::SendAction &action) {
|
||||
const auto size = group.list.files.size();
|
||||
auto latch = std::make_shared<TimedCountDownLatch>(size);
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
|
||||
auto groupId = std::make_shared<SendingAlbum>();
|
||||
groupId->groupId = base::RandomValue<uint64>();
|
||||
|
||||
crl::on_main([=, lst = std::move(group.list), caption = std::move(caption)]() mutable
|
||||
{
|
||||
session->api().sendFiles(std::move(lst), type, std::move(caption), groupId, action);
|
||||
});
|
||||
|
||||
|
||||
// probably need to handle
|
||||
// session->uploader().photoFailed()
|
||||
// and
|
||||
// session->uploader().documentFailed()
|
||||
// too
|
||||
|
||||
rpl::merge(
|
||||
session->uploader().documentReady(),
|
||||
session->uploader().photoReady()
|
||||
) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto)
|
||||
{
|
||||
return docOrPhoto.fullId.peer == action.history->peer->id;
|
||||
}) | rpl::start_with_next([&]
|
||||
{
|
||||
latch->countDown();
|
||||
},
|
||||
*lifetime);
|
||||
|
||||
latch->await(std::chrono::minutes(5 * size));
|
||||
base::take(lifetime)->destroy();
|
||||
}
|
||||
|
||||
void sendStickerSync(not_null<Main::Session*> session,
|
||||
Api::MessageToSend &message,
|
||||
not_null<DocumentData*> document) {
|
||||
auto &action = message.action;
|
||||
crl::on_main([&]
|
||||
{
|
||||
Api::SendExistingDocument(std::move(message), document, std::nullopt);
|
||||
});
|
||||
|
||||
waitForMsgSync(session, action);
|
||||
}
|
||||
|
||||
void sendVoiceSync(not_null<Main::Session*> session,
|
||||
const QByteArray &data,
|
||||
int64_t duration,
|
||||
bool video,
|
||||
const Api::SendAction &action) {
|
||||
crl::on_main([&]
|
||||
{
|
||||
session->api().sendVoiceMessage(data,
|
||||
QVector<signed char>(),
|
||||
duration,
|
||||
video,
|
||||
action);
|
||||
});
|
||||
waitForMsgSync(session, action);
|
||||
}
|
||||
|
||||
} // namespace AyuSync
|
49
Telegram/SourceFiles/ayu/features/forward/ayu_sync.h
Normal file
49
Telegram/SourceFiles/ayu/features/forward/ayu_sync.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2025
|
||||
#pragma once
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "history/history_item.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
|
||||
namespace AyuSync {
|
||||
|
||||
QString pathForSave(not_null<Main::Session*> session);
|
||||
QString filePath(not_null<Main::Session*> session, const Data::Media *media);
|
||||
void loadDocuments(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items);
|
||||
bool isMediaDownloadable(Data::Media *media);
|
||||
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message);
|
||||
|
||||
void sendDocumentSync(not_null<Main::Session*> session,
|
||||
Ui::PreparedGroup &group,
|
||||
SendMediaType type,
|
||||
TextWithTags &&caption,
|
||||
const Api::SendAction &action);
|
||||
|
||||
void sendStickerSync(not_null<Main::Session*> session,
|
||||
Api::MessageToSend &message,
|
||||
not_null<DocumentData*> document);
|
||||
void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action);
|
||||
void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<PhotoData*>, FullMsgId> &photos);
|
||||
void loadDocumentSync(not_null<Main::Session*> session, DocumentData *data, not_null<HistoryItem*> item);
|
||||
void forwardMessagesSync(not_null<Main::Session*> session,
|
||||
const std::vector<not_null<HistoryItem*>> &items,
|
||||
const ApiWrap::SendAction &action,
|
||||
Data::ForwardOptions options);
|
||||
void sendVoiceSync(not_null<Main::Session*> session,
|
||||
const QByteArray &data,
|
||||
int64_t duration,
|
||||
bool video,
|
||||
const Api::SendAction &action);
|
||||
}
|
|
@ -741,3 +741,34 @@ ID getUserIdFromPackId(uint64 id) {
|
|||
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
TextWithTags extractText(not_null<HistoryItem*> item) {
|
||||
TextWithTags result;
|
||||
|
||||
QString text;
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto poll = media->poll()) {
|
||||
text.append("\xF0\x9F\x93\x8A ") // 📊
|
||||
.append(poll->question.text).append("\n");
|
||||
for (const auto answer : poll->answers) {
|
||||
text.append("• ").append(answer.text.text).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.tags = TextUtilities::ConvertEntitiesToTextTags(item->originalText().entities);
|
||||
result.text = text.isEmpty() ? item->originalText().text : text;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool mediaDownloadable(Data::Media *media) {
|
||||
if (!media
|
||||
|| media->webpage() || media->poll() || media->game()
|
||||
|| media->invoice() || media->location() || media->paper()
|
||||
|| media->giveawayStart() || media->giveawayResults()
|
||||
|| media->sharedContact() || media->call()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_media_types.h"
|
||||
|
||||
using UsernameResolverCallback = Fn<void(const QString &, UserData *)>;
|
||||
|
||||
|
@ -55,3 +57,6 @@ void searchById(ID userId, Main::Session *session, bool retry, const UsernameRes
|
|||
void searchById(ID userId, Main::Session *session, const UsernameResolverCallback &callback);
|
||||
|
||||
ID getUserIdFromPackId(uint64 id);
|
||||
|
||||
TextWithTags extractText(not_null<HistoryItem*> item);
|
||||
bool mediaDownloadable(Data::Media* media);
|
|
@ -64,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
// AyuGram includes
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "ayu/features/forward/ayu_forward.h"
|
||||
|
||||
|
||||
class ShareBox::Inner final : public Ui::RpWidget {
|
||||
|
@ -1671,6 +1672,48 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
result,
|
||||
msgIds);
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
|
||||
|
||||
// AyuGram-changed
|
||||
const auto dismiss = [=]
|
||||
{
|
||||
if (show->valid()) {
|
||||
show->hideLayer();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (AyuForward::isFullAyuForwardNeeded(items.front())) {
|
||||
crl::async([=]{
|
||||
for (const auto thread : result) {
|
||||
AyuForward::forwardMessages(
|
||||
&history->owner().session(),
|
||||
Api::SendAction(thread, options),
|
||||
false,
|
||||
Data::ResolvedForwardDraft(items, forwardOptions));
|
||||
}
|
||||
});
|
||||
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
if (AyuForward::isAyuForwardNeeded(items)) {
|
||||
crl::async([=]
|
||||
{
|
||||
for (const auto thread : result) {
|
||||
AyuForward::intelligentForward(
|
||||
&history->owner().session(),
|
||||
Api::SendAction(thread, options),
|
||||
Data::ResolvedForwardDraft(items, forwardOptions));
|
||||
}
|
||||
});
|
||||
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
// AyuGram-changed
|
||||
|
||||
|
||||
for (const auto thread : result) {
|
||||
if (!comment.text.isEmpty()) {
|
||||
auto message = Api::MessageToSend(
|
||||
|
|
|
@ -63,6 +63,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtGui/QClipboard>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include "ayu/features/forward/ayu_forward.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Ui::Text;
|
||||
|
@ -1280,6 +1282,69 @@ std::unique_ptr<Ui::AbstractButton> BoostsToLiftWriteRestriction(
|
|||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::AbstractButton> AyuForwardWriteRestriction(
|
||||
not_null<QWidget *> parent,
|
||||
const PeerId &peer,
|
||||
const Main::Session &session) {
|
||||
using namespace Ui;
|
||||
|
||||
// status and part
|
||||
const auto pair = AyuForward::stateName(peer);
|
||||
|
||||
auto result = std::make_unique<FlatButton>(
|
||||
parent,
|
||||
QString(),
|
||||
st::historyComposeButton);
|
||||
const auto raw = result.get();
|
||||
|
||||
const auto title = CreateChild<FlatLabel>(
|
||||
raw,
|
||||
pair.first,
|
||||
st::frozenRestrictionTitle);
|
||||
title->setTextColorOverride(st::historyComposeButton.color->c);
|
||||
|
||||
|
||||
title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
title->show();
|
||||
const auto subtitle = CreateChild<FlatLabel>(
|
||||
raw,
|
||||
pair.second,
|
||||
st::frozenRestrictionSubtitle);
|
||||
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
subtitle->show();
|
||||
|
||||
|
||||
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
|
||||
const auto toggle = [&](auto &&widget, bool shown) {
|
||||
if (widget->isHidden() == shown) {
|
||||
widget->setVisible(shown);
|
||||
}
|
||||
};
|
||||
const auto small = 2 * st::defaultDialogRow.photoSize;
|
||||
const auto shown = (size.width() > small);
|
||||
|
||||
toggle(title, shown);
|
||||
toggle(subtitle, shown);
|
||||
|
||||
const auto skip = st::defaultDialogRow.padding.left();
|
||||
const auto available = size.width() - skip * 2;
|
||||
title->resizeToWidth(available);
|
||||
subtitle->resizeToWidth(available);
|
||||
const auto height = title->height() + subtitle->height();
|
||||
const auto top = (size.height() - height) / 2;
|
||||
title->moveToLeft(skip, top, size.width());
|
||||
subtitle->moveToLeft(skip, top + title->height(), size.width());
|
||||
|
||||
}, title->lifetime());
|
||||
|
||||
raw->setClickedCallback([&] {
|
||||
AyuForward::cancelForward(peer, session);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::AbstractButton> FrozenWriteRestriction(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
|
|
|
@ -202,7 +202,10 @@ enum class FrozenWriteRestrictionType {
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
FrozenWriteRestrictionType type,
|
||||
FreezeInfoStyleOverride st = {});
|
||||
|
||||
std::unique_ptr<Ui::AbstractButton> AyuForwardWriteRestriction(
|
||||
not_null<QWidget *> parent,
|
||||
const PeerId &peer,
|
||||
const Main::Session &session);
|
||||
void SelectTextInFieldWithMargins(
|
||||
not_null<Ui::InputField*> field,
|
||||
const TextSelection &selection);
|
||||
|
|
|
@ -620,6 +620,10 @@ bool ChannelData::canAddAdmins() const {
|
|||
|| (adminRights() & AdminRight::AddAdmins);
|
||||
}
|
||||
|
||||
bool ChannelData::isAyuNoForwards() const {
|
||||
return flags() & Flag::AyuNoForwards;
|
||||
}
|
||||
|
||||
bool ChannelData::allowsForwarding() const {
|
||||
return !(flags() & Flag::NoForwards);
|
||||
}
|
||||
|
|
|
@ -74,6 +74,8 @@ enum class ChannelDataFlag : uint64 {
|
|||
StargiftsAvailable = (1ULL << 36),
|
||||
PaidMessagesAvailable = (1ULL << 37),
|
||||
AutoTranslation = (1ULL << 38),
|
||||
|
||||
AyuNoForwards = (1ULL << 63),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||
|
@ -365,6 +367,7 @@ public:
|
|||
|
||||
// Like in ChatData.
|
||||
[[nodiscard]] bool allowsForwarding() const;
|
||||
[[nodiscard]] bool isAyuNoForwards() const;
|
||||
[[nodiscard]] bool canEditInformation() const;
|
||||
[[nodiscard]] bool canEditPermissions() const;
|
||||
[[nodiscard]] bool canEditUsername() const;
|
||||
|
|
|
@ -64,6 +64,10 @@ ChatAdminRightsInfo ChatData::defaultAdminRights(not_null<UserData*> user) {
|
|||
| (isCreator ? Flag::AddAdmins : Flag(0)));
|
||||
}
|
||||
|
||||
bool ChatData::isAyuNoForwards() const {
|
||||
return flags() & Flag::AyuNoForwards;
|
||||
}
|
||||
|
||||
bool ChatData::allowsForwarding() const {
|
||||
return !(flags() & Flag::NoForwards);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ enum class ChatDataFlag {
|
|||
CallNotEmpty = (1 << 6),
|
||||
CanSetUsername = (1 << 7),
|
||||
NoForwards = (1 << 8),
|
||||
|
||||
AyuNoForwards = (1 << 31),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ChatDataFlag) { return true; };
|
||||
using ChatDataFlags = base::flags<ChatDataFlag>;
|
||||
|
@ -99,6 +101,7 @@ public:
|
|||
|
||||
// Like in ChannelData.
|
||||
[[nodiscard]] bool allowsForwarding() const;
|
||||
[[nodiscard]] bool isAyuNoForwards() const;
|
||||
[[nodiscard]] bool canEditInformation() const;
|
||||
[[nodiscard]] bool canEditPermissions() const;
|
||||
[[nodiscard]] bool canEditUsername() const;
|
||||
|
|
|
@ -25,6 +25,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/features/forward/ayu_forward.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] ChatAdminRights ChatAdminRightsFlags(
|
||||
|
@ -118,6 +122,9 @@ bool CanSendAnyOf(
|
|||
not_null<const PeerData*> peer,
|
||||
ChatRestrictions rights,
|
||||
bool forbidInForums) {
|
||||
if (AyuForward::isForwarding(peer->id)) {
|
||||
return false;
|
||||
}
|
||||
if (peer->session().frozen()
|
||||
&& !peer->isFreezeAppealChat()) {
|
||||
return false;
|
||||
|
@ -180,6 +187,11 @@ bool CanSendAnyOf(
|
|||
SendError RestrictionError(
|
||||
not_null<PeerData*> peer,
|
||||
ChatRestriction restriction) {
|
||||
if (AyuForward::isForwarding(peer->id)) {
|
||||
return SendError({
|
||||
.text = AyuForward::stateName(peer->id).first + "\n" + AyuForward::stateName(peer->id).second,
|
||||
});
|
||||
}
|
||||
using Flag = ChatRestriction;
|
||||
if (peer->session().frozen()
|
||||
&& !peer->isFreezeAppealChat()) {
|
||||
|
|
|
@ -1386,6 +1386,18 @@ Data::ForumTopic *PeerData::forumTopicFor(MsgId rootId) const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool PeerData::isAyuNoForwards() const {
|
||||
if (const auto user = asUser()) {
|
||||
return false;
|
||||
} else if (const auto channel = asChannel()) {
|
||||
return channel->isAyuNoForwards();
|
||||
} else if (const auto chat = asChat()) {
|
||||
return chat->isAyuNoForwards();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool PeerData::allowsForwarding() const {
|
||||
if (isUser()) {
|
||||
return true;
|
||||
|
|
|
@ -264,6 +264,7 @@ public:
|
|||
return _notify;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isAyuNoForwards() const;
|
||||
[[nodiscard]] bool allowsForwarding() const;
|
||||
[[nodiscard]] Data::RestrictionCheckResult amRestricted(
|
||||
ChatRestriction right) const;
|
||||
|
|
|
@ -860,7 +860,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
|||
&& chat->groupCall()->fullCount() > 0))
|
||||
? Flag::CallNotEmpty
|
||||
: Flag())
|
||||
| (data.is_noforwards() ? Flag::NoForwards : Flag());
|
||||
| (data.is_noforwards() ? Flag::NoForwards : Flag())
|
||||
| (data.is_ayuNoforwards() ? Flag::AyuNoForwards : Flag());
|
||||
chat->setFlags((chat->flags() & ~flagsMask) | flagsSet);
|
||||
chat->count = data.vparticipants_count().v;
|
||||
|
||||
|
@ -1017,7 +1018,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
|||
&& data.is_stories_hidden())
|
||||
? Flag::StoriesHidden
|
||||
: Flag())
|
||||
| (data.is_autotranslation() ? Flag::AutoTranslation : Flag());
|
||||
| (data.is_autotranslation() ? Flag::AutoTranslation : Flag())
|
||||
| (data.is_ayuNoforwards() ? Flag::AyuNoForwards : Flag());
|
||||
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
|
||||
channel->setBotVerifyDetailsIcon(
|
||||
data.vbot_verification_icon().value_or_empty());
|
||||
|
|
|
@ -351,6 +351,9 @@ enum class MessageFlag : uint64 {
|
|||
ReactionsAllowed = (1ULL << 50),
|
||||
|
||||
HideDisplayDate = (1ULL << 51),
|
||||
|
||||
|
||||
AyuNoForwards = (1ULL << 63),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
|
|
@ -2785,7 +2785,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
const auto itemId = item->fullId();
|
||||
const auto blockSender = item->history()->peer->isRepliesChat();
|
||||
if (isUponSelected != -2) {
|
||||
if (item->allowsForward() && !item->isDeleted()) {
|
||||
if (item->allowsForward()) {
|
||||
_menu->addAction(tr::lng_context_forward_msg(tr::now), [=] {
|
||||
forwardItem(itemId);
|
||||
}, &st::menuIconForward);
|
||||
|
@ -3027,7 +3027,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
}, &st::menuIconSelect);
|
||||
} else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->isRegular())) {
|
||||
if (isUponSelected != -2) {
|
||||
if (canForward && !item->isDeleted()) {
|
||||
if (canForward) {
|
||||
_menu->addAction(tr::lng_context_forward_msg(tr::now), [=] {
|
||||
forwardAsGroup(itemId);
|
||||
}, &st::menuIconForward);
|
||||
|
@ -3954,7 +3954,7 @@ auto HistoryInner::getSelectionState() const
|
|||
if (selected.first->canDelete()) {
|
||||
++result.canDeleteCount;
|
||||
}
|
||||
if (selected.first->allowsForward() && !selected.first->isDeleted()) {
|
||||
if (selected.first->allowsForward()) {
|
||||
++result.canForwardCount;
|
||||
}
|
||||
} else if (selected.second.from != selected.second.to) {
|
||||
|
|
|
@ -1767,6 +1767,10 @@ bool HistoryItem::isSponsored() const {
|
|||
return _flags & MessageFlag::Sponsored;
|
||||
}
|
||||
|
||||
bool HistoryItem::isAyuNoForwards() const {
|
||||
return _flags & MessageFlag::AyuNoForwards;
|
||||
}
|
||||
|
||||
bool HistoryItem::skipNotification() const {
|
||||
if (isSilent() && (_flags & MessageFlag::IsContactSignUp)) {
|
||||
return true;
|
||||
|
|
|
@ -207,6 +207,9 @@ public:
|
|||
[[nodiscard]] bool isFromScheduled() const;
|
||||
[[nodiscard]] bool isScheduled() const;
|
||||
[[nodiscard]] bool isSponsored() const;
|
||||
|
||||
[[nodiscard]] bool isAyuNoForwards() const;
|
||||
|
||||
[[nodiscard]] bool skipNotification() const;
|
||||
[[nodiscard]] bool isUserpicSuggestion() const;
|
||||
[[nodiscard]] BusinessShortcutId shortcutId() const;
|
||||
|
|
|
@ -767,6 +767,7 @@ MessageFlags FlagsFromMTP(
|
|||
| ((flags & MTP::f_views) ? Flag::HasViews : Flag())
|
||||
// AyuGram: removed
|
||||
// | ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag())
|
||||
| (flags & MTP::f_noforwards ? Flag::AyuNoForwards : Flag())
|
||||
| ((flags & MTP::f_invert_media) ? Flag::InvertMedia : Flag())
|
||||
| ((flags & MTP::f_video_processing_pending)
|
||||
? Flag::EstimatedDate
|
||||
|
|
|
@ -187,6 +187,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "ayu/features/messageshot/message_shot.h"
|
||||
#include "ayu/features/forward/ayu_forward.h"
|
||||
#include "ayu/ui/boxes/message_shot_box.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
|
@ -6758,7 +6759,9 @@ void HistoryWidget::updateSendRestriction() {
|
|||
return;
|
||||
}
|
||||
_sendRestrictionKey = restriction.text;
|
||||
if (!restriction) {
|
||||
if (AyuForward::isForwarding(_peer->id)) {
|
||||
_sendRestriction = AyuForwardWriteRestriction(this, _peer->id, session());
|
||||
} else if (!restriction) {
|
||||
_sendRestriction = nullptr;
|
||||
} else if (restriction.frozen) {
|
||||
const auto show = controller()->uiShow();
|
||||
|
|
|
@ -413,10 +413,6 @@ bool AddForwardMessageAction(
|
|||
const ContextMenuRequest &request,
|
||||
not_null<ListWidget*> list) {
|
||||
const auto item = request.item;
|
||||
if (item && item->isDeleted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!request.selectedItems.empty()) {
|
||||
return false;
|
||||
} else if (!item || !item->allowsForward()) {
|
||||
|
@ -641,7 +637,7 @@ bool AddReplyToMessageAction(
|
|||
? Data::CanSendAnything(topic)
|
||||
: Data::CanSendAnything(peer);
|
||||
const auto canReply = canSendReply || item->allowsForward();
|
||||
if (!canReply || item->isDeleted()) {
|
||||
if (!canReply) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1358,7 +1358,7 @@ bool ListWidget::addToSelection(
|
|||
return false;
|
||||
}
|
||||
iterator->second.canDelete = item->canDelete();
|
||||
iterator->second.canForward = item->allowsForward() && !item->isDeleted();
|
||||
iterator->second.canForward = item->allowsForward();
|
||||
iterator->second.canSendNow = item->allowsSendNow();
|
||||
iterator->second.canReschedule = item->allowsReschedule();
|
||||
return true;
|
||||
|
|
|
@ -96,6 +96,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/features/forward/ayu_forward.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
void ClearBotStartToken(PeerData *peer) {
|
||||
|
@ -563,7 +567,9 @@ bool MainWidget::setForwardDraft(
|
|||
.forward = &items,
|
||||
.ignoreSlowmodeCountdown = true,
|
||||
});
|
||||
if (error) {
|
||||
// allow opening chat that
|
||||
// already have some forward task
|
||||
if (error && !AyuForward::isForwarding(history->peer->id)) {
|
||||
Data::ShowSendErrorToast(_controller, history->peer, error);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
// AyuGram includes
|
||||
#include "styles/style_ayu_icons.h"
|
||||
#include "ayu/ui/context_menu/context_menu.h"
|
||||
|
||||
#include "ayu/features/forward/ayu_forward.h"
|
||||
|
||||
namespace Window {
|
||||
namespace {
|
||||
|
@ -2586,9 +2586,17 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
|||
std::move(comment),
|
||||
options,
|
||||
state->box->forwardOptionsData());
|
||||
if (!state->submit && successCallback) {
|
||||
|
||||
// AyuGram-changed
|
||||
|
||||
// workaround for deselecting messages when using AyuForward
|
||||
const auto items = history->owner().idsToItems(msgIds);
|
||||
auto ayuForwarding = AyuForward::isAyuForwardNeeded(items) || AyuForward::isFullAyuForwardNeeded(items.front());
|
||||
|
||||
if (!state->submit || ayuForwarding && successCallback) {
|
||||
successCallback();
|
||||
}
|
||||
// AyuGram-changed
|
||||
};
|
||||
|
||||
const auto sendMenuType = [=] {
|
||||
|
|
Loading…
Add table
Reference in a new issue