mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 21:27:07 +02:00
feat: store text deleted messages in db
This commit is contained in:
parent
7caf421074
commit
625f9ef816
20 changed files with 14341 additions and 7849 deletions
|
@ -136,12 +136,12 @@ set(ayugram_files
|
|||
ayu/ui/context_menu/context_menu.h
|
||||
ayu/ui/context_menu/menu_item_subtext.cpp
|
||||
ayu/ui/context_menu/menu_item_subtext.h
|
||||
ayu/ui/sections/edited/edited_log_inner.cpp
|
||||
ayu/ui/sections/edited/edited_log_inner.h
|
||||
ayu/ui/sections/edited/edited_log_item.cpp
|
||||
ayu/ui/sections/edited/edited_log_item.h
|
||||
ayu/ui/sections/edited/edited_log_section.cpp
|
||||
ayu/ui/sections/edited/edited_log_section.h
|
||||
ayu/ui/message_history/history_inner.cpp
|
||||
ayu/ui/message_history/history_inner.h
|
||||
ayu/ui/message_history/history_item.cpp
|
||||
ayu/ui/message_history/history_item.h
|
||||
ayu/ui/message_history/history_section.cpp
|
||||
ayu/ui/message_history/history_section.h
|
||||
ayu/ui/boxes/edit_deleted_mark.cpp
|
||||
ayu/ui/boxes/edit_deleted_mark.h
|
||||
ayu/ui/boxes/edit_edited_mark.cpp
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
using namespace sqlite_orm;
|
||||
auto storage = make_storage(
|
||||
"./tdata/ayudata.db",
|
||||
make_table(
|
||||
make_table<DeletedMessage>(
|
||||
"DeletedMessage",
|
||||
make_column("fakeId", &DeletedMessage::fakeId, primary_key().autoincrement()),
|
||||
make_column("userId", &DeletedMessage::userId),
|
||||
|
@ -52,7 +52,7 @@ auto storage = make_storage(
|
|||
make_column("documentAttributesSerialized", &DeletedMessage::documentAttributesSerialized),
|
||||
make_column("mimeType", &DeletedMessage::mimeType)
|
||||
),
|
||||
make_table(
|
||||
make_table<EditedMessage>(
|
||||
"EditedMessage",
|
||||
make_column("fakeId", &EditedMessage::fakeId, primary_key().autoincrement()),
|
||||
make_column("userId", &EditedMessage::userId),
|
||||
|
@ -88,7 +88,7 @@ auto storage = make_storage(
|
|||
make_column("documentAttributesSerialized", &EditedMessage::documentAttributesSerialized),
|
||||
make_column("mimeType", &EditedMessage::mimeType)
|
||||
),
|
||||
make_table(
|
||||
make_table<DeletedDialog>(
|
||||
"DeletedDialog",
|
||||
make_column("fakeId", &DeletedDialog::fakeId, primary_key().autoincrement()),
|
||||
make_column("userId", &DeletedDialog::userId),
|
||||
|
@ -100,7 +100,7 @@ auto storage = make_storage(
|
|||
make_column("flags", &DeletedDialog::flags),
|
||||
make_column("entityCreateDate", &DeletedDialog::entityCreateDate)
|
||||
),
|
||||
make_table(
|
||||
make_table<RegexFilter>(
|
||||
"RegexFilter",
|
||||
make_column("id", &RegexFilter::id),
|
||||
make_column("text", &RegexFilter::text),
|
||||
|
@ -108,13 +108,13 @@ auto storage = make_storage(
|
|||
make_column("caseInsensitive", &RegexFilter::caseInsensitive),
|
||||
make_column("dialogId", &RegexFilter::dialogId)
|
||||
),
|
||||
make_table(
|
||||
make_table<RegexFilterGlobalExclusion>(
|
||||
"RegexFilterGlobalExclusion",
|
||||
make_column("fakeId", &RegexFilterGlobalExclusion::fakeId, primary_key().autoincrement()),
|
||||
make_column("dialogId", &RegexFilterGlobalExclusion::dialogId),
|
||||
make_column("filterId", &RegexFilterGlobalExclusion::filterId)
|
||||
),
|
||||
make_table(
|
||||
make_table<SpyMessageRead>(
|
||||
"SpyMessageRead",
|
||||
make_column("fakeId", &SpyMessageRead::fakeId, primary_key().autoincrement()),
|
||||
make_column("userId", &SpyMessageRead::userId),
|
||||
|
@ -122,7 +122,7 @@ auto storage = make_storage(
|
|||
make_column("messageId", &SpyMessageRead::messageId),
|
||||
make_column("entityCreateDate", &SpyMessageRead::entityCreateDate)
|
||||
),
|
||||
make_table(
|
||||
make_table<SpyMessageContentsRead>(
|
||||
"SpyMessageContentsRead",
|
||||
make_column("fakeId", &SpyMessageContentsRead::fakeId, primary_key().autoincrement()),
|
||||
make_column("userId", &SpyMessageContentsRead::userId),
|
||||
|
@ -135,7 +135,7 @@ auto storage = make_storage(
|
|||
namespace AyuDatabase {
|
||||
|
||||
void moveCurrentDatabase() {
|
||||
auto time = base::unixtime::now();
|
||||
const auto time = base::unixtime::now();
|
||||
|
||||
if (QFile::exists("./tdata/ayudata.db")) {
|
||||
QFile::rename("./tdata/ayudata.db", QString("./tdata/ayudata_%1.db").arg(time));
|
||||
|
@ -190,18 +190,22 @@ void addEditedMessage(const EditedMessage &message) {
|
|||
storage.begin_transaction();
|
||||
storage.insert(message);
|
||||
storage.commit();
|
||||
} catch (std::exception& ex) {
|
||||
} catch (std::exception &ex) {
|
||||
LOG(("Failed to save edited message for some reason: %1").arg(ex.what()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId) {
|
||||
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId, ID minId, ID maxId, int totalLimit) {
|
||||
return storage.get_all<EditedMessage>(
|
||||
where(
|
||||
c(&EditedMessage::userId) == userId and
|
||||
c(&EditedMessage::dialogId) == dialogId and
|
||||
c(&EditedMessage::messageId) == messageId
|
||||
)
|
||||
column<EditedMessage>(&EditedMessage::userId) == userId and
|
||||
column<EditedMessage>(&EditedMessage::dialogId) == dialogId and
|
||||
column<EditedMessage>(&EditedMessage::messageId) == messageId and
|
||||
(column<EditedMessage>(&EditedMessage::fakeId) > minId or minId == 0) and
|
||||
(column<EditedMessage>(&EditedMessage::fakeId) < maxId or maxId == 0)
|
||||
),
|
||||
order_by(column<EditedMessage>(&EditedMessage::fakeId)).desc(),
|
||||
limit(totalLimit)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -209,15 +213,50 @@ bool hasRevisions(ID userId, ID dialogId, ID messageId) {
|
|||
try {
|
||||
return storage.count<EditedMessage>(
|
||||
where(
|
||||
c(&EditedMessage::userId) == userId and
|
||||
c(&EditedMessage::dialogId) == dialogId and
|
||||
c(&EditedMessage::messageId) == messageId
|
||||
column<EditedMessage>(&EditedMessage::userId) == userId and
|
||||
column<EditedMessage>(&EditedMessage::dialogId) == dialogId and
|
||||
column<EditedMessage>(&EditedMessage::messageId) == messageId
|
||||
)
|
||||
) > 0;
|
||||
} catch (std::exception& ex) {
|
||||
} catch (std::exception &ex) {
|
||||
LOG(("Failed to check if message has revisions: %1").arg(ex.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void addDeletedMessage(const DeletedMessage &message) {
|
||||
try {
|
||||
storage.begin_transaction();
|
||||
storage.insert(message);
|
||||
storage.commit();
|
||||
} catch (std::exception &ex) {
|
||||
LOG(("Failed to save edited message for some reason: %1").arg(ex.what()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DeletedMessage> getDeletedMessages(ID dialogId, ID minId, ID maxId, int totalLimit) {
|
||||
return storage.get_all<DeletedMessage>(
|
||||
where(
|
||||
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
|
||||
(column<DeletedMessage>(&DeletedMessage::messageId) > minId or minId == 0) and
|
||||
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0)
|
||||
),
|
||||
order_by(column<DeletedMessage>(&DeletedMessage::messageId)).desc(),
|
||||
limit(totalLimit)
|
||||
);
|
||||
}
|
||||
|
||||
bool hasDeletedMessages(ID dialogId) {
|
||||
try {
|
||||
return storage.count<DeletedMessage>(
|
||||
where(
|
||||
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId
|
||||
)
|
||||
) > 0;
|
||||
} catch (std::exception &ex) {
|
||||
LOG(("Failed to check if dialog has deleted message: %1").arg(ex.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,11 @@ namespace AyuDatabase {
|
|||
void initialize();
|
||||
|
||||
void addEditedMessage(const EditedMessage &message);
|
||||
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId);
|
||||
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId, ID minId, ID maxId, int totalLimit);
|
||||
bool hasRevisions(ID userId, ID dialogId, ID messageId);
|
||||
|
||||
void addDeletedMessage(const DeletedMessage &message);
|
||||
std::vector<DeletedMessage> getDeletedMessages(ID dialogId, ID minId, ID maxId, int totalLimit);
|
||||
bool hasDeletedMessages(ID dialogId);
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#define ID long long
|
||||
|
||||
template<typename TableName>
|
||||
class AyuMessageBase
|
||||
{
|
||||
public:
|
||||
|
@ -49,9 +48,13 @@ public:
|
|||
std::string mimeType;
|
||||
};
|
||||
|
||||
using DeletedMessage = AyuMessageBase<struct DeletedMessageTag>;
|
||||
class DeletedMessage : public AyuMessageBase
|
||||
{
|
||||
};
|
||||
|
||||
using EditedMessage = AyuMessageBase<struct EditedMessageTag>;
|
||||
class EditedMessage : public AyuMessageBase
|
||||
{
|
||||
};
|
||||
|
||||
class DeletedDialog
|
||||
{
|
||||
|
|
|
@ -24,7 +24,17 @@
|
|||
|
||||
namespace AyuMessages {
|
||||
|
||||
void map(HistoryMessageEdition &edition, not_null<HistoryItem*> item, EditedMessage &message) {
|
||||
template<typename DerivedMessage>
|
||||
std::vector<AyuMessageBase> convertToBase(const std::vector<DerivedMessage> &messages) {
|
||||
std::vector<AyuMessageBase> based;
|
||||
based.reserve(messages.size());
|
||||
for (const auto &msg : messages) {
|
||||
based.push_back(static_cast<AyuMessageBase>(msg));
|
||||
}
|
||||
return based;
|
||||
}
|
||||
|
||||
void map(not_null<HistoryItem*> item, AyuMessageBase &message) {
|
||||
message.userId = item->history()->owner().session().userId().bare;
|
||||
message.dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||
message.groupedId = item->groupId().value;
|
||||
|
@ -62,17 +72,21 @@ void map(HistoryMessageEdition &edition, not_null<HistoryItem*> item, EditedMess
|
|||
|
||||
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item) {
|
||||
EditedMessage message;
|
||||
map(edition, item, message);
|
||||
map(item, message);
|
||||
|
||||
if (message.text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AyuDatabase::addEditedMessage(message);
|
||||
}
|
||||
|
||||
std::vector<EditedMessage> getEditedMessages(not_null<HistoryItem*> item) {
|
||||
std::vector<AyuMessageBase> getEditedMessages(not_null<HistoryItem*> item, ID minId, ID maxId, int totalLimit) {
|
||||
auto userId = item->history()->owner().session().userId().bare;
|
||||
auto dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||
auto msgId = item->id.bare;
|
||||
|
||||
return AyuDatabase::getEditedMessages(userId, dialogId, msgId);
|
||||
return convertToBase(AyuDatabase::getEditedMessages(userId, dialogId, msgId, minId, maxId, totalLimit));
|
||||
}
|
||||
|
||||
bool hasRevisions(not_null<HistoryItem*> item) {
|
||||
|
@ -83,4 +97,23 @@ bool hasRevisions(not_null<HistoryItem*> item) {
|
|||
return AyuDatabase::hasRevisions(userId, dialogId, msgId);
|
||||
}
|
||||
|
||||
void addDeletedMessage(not_null<HistoryItem*> item) {
|
||||
DeletedMessage message;
|
||||
map(item, message);
|
||||
|
||||
if (message.text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AyuDatabase::addDeletedMessage(message);
|
||||
}
|
||||
|
||||
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID minId, ID maxId, int totalLimit) {
|
||||
return convertToBase(AyuDatabase::getDeletedMessages(getDialogIdFromPeer(peer), minId, maxId, totalLimit));
|
||||
}
|
||||
|
||||
bool hasDeletedMessages(not_null<PeerData*> peer) {
|
||||
return AyuDatabase::hasDeletedMessages(getDialogIdFromPeer(peer));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,11 @@
|
|||
namespace AyuMessages {
|
||||
|
||||
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item);
|
||||
std::vector<EditedMessage> getEditedMessages(not_null<HistoryItem*> item);
|
||||
std::vector<AyuMessageBase> getEditedMessages(not_null<HistoryItem*> item, ID minId, ID maxId, int totalLimit);
|
||||
bool hasRevisions(not_null<HistoryItem*> item);
|
||||
|
||||
void addDeletedMessage(not_null<HistoryItem*> item);
|
||||
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID minId, ID maxId, int totalLimit);
|
||||
bool hasDeletedMessages(not_null<PeerData*> peer);
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -146,9 +146,9 @@ extern "C" {
|
|||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.45.3"
|
||||
#define SQLITE_VERSION_NUMBER 3045003
|
||||
#define SQLITE_SOURCE_ID "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355"
|
||||
#define SQLITE_VERSION "3.46.1"
|
||||
#define SQLITE_VERSION_NUMBER 3046001
|
||||
#define SQLITE_SOURCE_ID "2024-08-13 09:16:08 c9c2ab54ba1f5f46360f1b4f35d849cd3f080e6fc2b6c60e91b16c63f69a1e33"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
|
@ -764,11 +764,11 @@ struct sqlite3_file {
|
|||
** </ul>
|
||||
** xLock() upgrades the database file lock. In other words, xLock() moves the
|
||||
** database file lock in the direction NONE toward EXCLUSIVE. The argument to
|
||||
** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
|
||||
** xLock() is always one of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
|
||||
** SQLITE_LOCK_NONE. If the database file lock is already at or above the
|
||||
** requested lock, then the call to xLock() is a no-op.
|
||||
** xUnlock() downgrades the database file lock to either SHARED or NONE.
|
||||
* If the lock is already at or below the requested lock state, then the call
|
||||
** If the lock is already at or below the requested lock state, then the call
|
||||
** to xUnlock() is a no-op.
|
||||
** The xCheckReservedLock() method checks whether any database connection,
|
||||
** either in this process or in some other process, is holding a RESERVED,
|
||||
|
@ -3305,8 +3305,8 @@ SQLITE_API int sqlite3_set_authorizer(
|
|||
#define SQLITE_RECURSIVE 33 /* NULL NULL */
|
||||
|
||||
/*
|
||||
** CAPI3REF: Tracing And Profiling Functions
|
||||
** METHOD: sqlite3
|
||||
** CAPI3REF: Deprecated Tracing And Profiling Functions
|
||||
** DEPRECATED
|
||||
**
|
||||
** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
|
||||
** instead of the routines described here.
|
||||
|
@ -6887,6 +6887,12 @@ SQLITE_API int sqlite3_autovacuum_pages(
|
|||
** The exceptions defined in this paragraph might change in a future
|
||||
** release of SQLite.
|
||||
**
|
||||
** Whether the update hook is invoked before or after the
|
||||
** corresponding change is currently unspecified and may differ
|
||||
** depending on the type of change. Do not rely on the order of the
|
||||
** hook call with regards to the final result of the operation which
|
||||
** triggers the hook.
|
||||
**
|
||||
** The update hook implementation must not do anything that will modify
|
||||
** the database connection that invoked the update hook. Any actions
|
||||
** to modify the database connection must be deferred until after the
|
||||
|
@ -8357,7 +8363,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|||
** The sqlite3_keyword_count() interface returns the number of distinct
|
||||
** keywords understood by SQLite.
|
||||
**
|
||||
** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and
|
||||
** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and
|
||||
** makes *Z point to that keyword expressed as UTF8 and writes the number
|
||||
** of bytes in the keyword into *L. The string that *Z points to is not
|
||||
** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns
|
||||
|
@ -9936,24 +9942,45 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||
** <li value="2"><p>
|
||||
** ^(If the sqlite3_vtab_distinct() interface returns 2, that means
|
||||
** that the query planner does not need the rows returned in any particular
|
||||
** order, as long as rows with the same values in all "aOrderBy" columns
|
||||
** are adjacent.)^ ^(Furthermore, only a single row for each particular
|
||||
** combination of values in the columns identified by the "aOrderBy" field
|
||||
** needs to be returned.)^ ^It is always ok for two or more rows with the same
|
||||
** values in all "aOrderBy" columns to be returned, as long as all such rows
|
||||
** are adjacent. ^The virtual table may, if it chooses, omit extra rows
|
||||
** that have the same value for all columns identified by "aOrderBy".
|
||||
** ^However omitting the extra rows is optional.
|
||||
** order, as long as rows with the same values in all columns identified
|
||||
** by "aOrderBy" are adjacent.)^ ^(Furthermore, when two or more rows
|
||||
** contain the same values for all columns identified by "colUsed", all but
|
||||
** one such row may optionally be omitted from the result.)^
|
||||
** The virtual table is not required to omit rows that are duplicates
|
||||
** over the "colUsed" columns, but if the virtual table can do that without
|
||||
** too much extra effort, it could potentially help the query to run faster.
|
||||
** This mode is used for a DISTINCT query.
|
||||
** <li value="3"><p>
|
||||
** ^(If the sqlite3_vtab_distinct() interface returns 3, that means
|
||||
** that the query planner needs only distinct rows but it does need the
|
||||
** rows to be sorted.)^ ^The virtual table implementation is free to omit
|
||||
** rows that are identical in all aOrderBy columns, if it wants to, but
|
||||
** it is not required to omit any rows. This mode is used for queries
|
||||
** ^(If the sqlite3_vtab_distinct() interface returns 3, that means the
|
||||
** virtual table must return rows in the order defined by "aOrderBy" as
|
||||
** if the sqlite3_vtab_distinct() interface had returned 0. However if
|
||||
** two or more rows in the result have the same values for all columns
|
||||
** identified by "colUsed", then all but one such row may optionally be
|
||||
** omitted.)^ Like when the return value is 2, the virtual table
|
||||
** is not required to omit rows that are duplicates over the "colUsed"
|
||||
** columns, but if the virtual table can do that without
|
||||
** too much extra effort, it could potentially help the query to run faster.
|
||||
** This mode is used for queries
|
||||
** that have both DISTINCT and ORDER BY clauses.
|
||||
** </ol>
|
||||
**
|
||||
** <p>The following table summarizes the conditions under which the
|
||||
** virtual table is allowed to set the "orderByConsumed" flag based on
|
||||
** the value returned by sqlite3_vtab_distinct(). This table is a
|
||||
** restatement of the previous four paragraphs:
|
||||
**
|
||||
** <table border=1 cellspacing=0 cellpadding=10 width="90%">
|
||||
** <tr>
|
||||
** <td valign="top">sqlite3_vtab_distinct() return value
|
||||
** <td valign="top">Rows are returned in aOrderBy order
|
||||
** <td valign="top">Rows with the same value in all aOrderBy columns are adjacent
|
||||
** <td valign="top">Duplicates over all colUsed columns may be omitted
|
||||
** <tr><td>0<td>yes<td>yes<td>no
|
||||
** <tr><td>1<td>no<td>yes<td>no
|
||||
** <tr><td>2<td>no<td>yes<td>yes
|
||||
** <tr><td>3<td>yes<td>yes<td>yes
|
||||
** </table>
|
||||
**
|
||||
** ^For the purposes of comparing virtual table output values to see if the
|
||||
** values are same value for sorting purposes, two NULL values are considered
|
||||
** to be the same. In other words, the comparison operator is "IS"
|
||||
|
@ -11998,6 +12025,30 @@ SQLITE_API int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const c
|
|||
*/
|
||||
SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Add A Single Change To A Changegroup
|
||||
** METHOD: sqlite3_changegroup
|
||||
**
|
||||
** This function adds the single change currently indicated by the iterator
|
||||
** passed as the second argument to the changegroup object. The rules for
|
||||
** adding the change are just as described for [sqlite3changegroup_add()].
|
||||
**
|
||||
** If the change is successfully added to the changegroup, SQLITE_OK is
|
||||
** returned. Otherwise, an SQLite error code is returned.
|
||||
**
|
||||
** The iterator must point to a valid entry when this function is called.
|
||||
** If it does not, SQLITE_ERROR is returned and no change is added to the
|
||||
** changegroup. Additionally, the iterator must not have been opened with
|
||||
** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also
|
||||
** returned.
|
||||
*/
|
||||
SQLITE_API int sqlite3changegroup_add_change(
|
||||
sqlite3_changegroup*,
|
||||
sqlite3_changeset_iter*
|
||||
);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
|
||||
** METHOD: sqlite3_changegroup
|
||||
|
@ -12802,8 +12853,8 @@ struct Fts5PhraseIter {
|
|||
** EXTENSION API FUNCTIONS
|
||||
**
|
||||
** xUserData(pFts):
|
||||
** Return a copy of the context pointer the extension function was
|
||||
** registered with.
|
||||
** Return a copy of the pUserData pointer passed to the xCreateFunction()
|
||||
** API when the extension function was registered.
|
||||
**
|
||||
** xColumnTotalSize(pFts, iCol, pnToken):
|
||||
** If parameter iCol is less than zero, set output variable *pnToken
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,25 +6,24 @@
|
|||
// Copyright @Radolyn, 2024
|
||||
#include "ayu/ui/context_menu/context_menu.h"
|
||||
|
||||
#include <styles/style_menu_icons.h>
|
||||
|
||||
#include "lang_auto.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/ayu_state.h"
|
||||
#include "../../data/messages_storage.h"
|
||||
#include "ayu/data/messages_storage.h"
|
||||
#include "ayu/ui/context_menu/menu_item_subtext.h"
|
||||
#include "ayu/utils/qt_key_modifiers_extended.h"
|
||||
#include "history/history_item_components.h"
|
||||
|
||||
#include "core/mime_type.h"
|
||||
#include "styles/style_ayu_icons.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
|
||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
||||
#include "ayu/ui/message_history/history_section.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
|
@ -37,13 +36,36 @@ bool needToShowItem(int state) {
|
|||
return state == 1 || (state == 2 && base::IsExtendedContextMenuModifierPressed());
|
||||
}
|
||||
|
||||
void AddDeletedMessagesActions(PeerData *peerData,
|
||||
not_null<Window::SessionController*> sessionController,
|
||||
const Dialogs::EntryState &entryState,
|
||||
const Window::PeerMenuCallback &addCallback) {
|
||||
if (!peerData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto has = AyuMessages::hasDeletedMessages(peerData);
|
||||
if (!has) {
|
||||
return;
|
||||
}
|
||||
|
||||
addCallback(tr::ayu_ViewDeletedMenuText(tr::now),
|
||||
[=]
|
||||
{
|
||||
sessionController->session().tryResolveWindow()
|
||||
->showSection(std::make_shared<MessageHistory::SectionMemento>(peerData));
|
||||
},
|
||||
&st::menuIconArchive);
|
||||
}
|
||||
|
||||
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
if (AyuMessages::hasRevisions(item)) {
|
||||
menu->addAction(tr::ayu_EditsHistoryMenuText(tr::now),
|
||||
[=]
|
||||
{
|
||||
item->history()->session().tryResolveWindow()
|
||||
->showSection(std::make_shared<EditedLog::SectionMemento>(item->history()->peer, item));
|
||||
->showSection(
|
||||
std::make_shared<MessageHistory::SectionMemento>(item->history()->peer, item));
|
||||
},
|
||||
&st::ayuEditsHistoryIcon);
|
||||
}
|
||||
|
@ -120,7 +142,7 @@ void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
|||
if (!containsSingleCustomEmojiPack && emojiPacks.size() > 1) {
|
||||
const auto author = emojiPacks.front().id >> 32;
|
||||
auto sameAuthor = true;
|
||||
for (const auto &pack : emojiPacks) {
|
||||
for (const auto &pack : emojiPacks) {
|
||||
if (pack.id >> 32 != author) {
|
||||
sameAuthor = false;
|
||||
break;
|
||||
|
|
|
@ -8,11 +8,18 @@
|
|||
|
||||
#include "data/data_document.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace AyuUi {
|
||||
|
||||
bool needToShowItem(int state);
|
||||
|
||||
void AddDeletedMessagesActions(PeerData *peerData,
|
||||
not_null<Window::SessionController*> sessionController,
|
||||
const Dialogs::EntryState &entryState,
|
||||
const Window::PeerMenuCallback &addCallback);
|
||||
|
||||
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||
void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2024
|
||||
#include "ayu/ui/sections/edited/edited_log_inner.h"
|
||||
#include "ayu/ui/message_history/history_inner.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "api/api_attached_stickers.h"
|
||||
#include "ayu/data/messages_storage.h"
|
||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
||||
#include "ayu/ui/message_history/history_section.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
|
@ -59,17 +59,14 @@
|
|||
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace EditedLog {
|
||||
namespace MessageHistory {
|
||||
namespace {
|
||||
|
||||
// If we require to support more admins we'll have to rewrite this anyway.
|
||||
constexpr auto kMaxChannelAdmins = 200;
|
||||
|
||||
constexpr auto kScrollDateHideTimeout = 1000;
|
||||
|
||||
constexpr auto kEventsFirstPage = 20;
|
||||
constexpr auto kMessagesFirstPage = 20;
|
||||
|
||||
constexpr auto kEventsPerPage = 50;
|
||||
constexpr auto kMessagesPerPage = 30;
|
||||
|
||||
constexpr auto kClearUserpicsAfter = 50;
|
||||
|
||||
|
@ -249,7 +246,7 @@ InnerWidget::InnerWidget(
|
|||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item)
|
||||
HistoryItem *item)
|
||||
: RpWidget(parent),
|
||||
_controller(controller),
|
||||
_peer(peer),
|
||||
|
@ -380,9 +377,7 @@ void InnerWidget::visibleTopBottomUpdated(
|
|||
}
|
||||
|
||||
updateVisibleTopItem();
|
||||
if (_items.size() == 0) {
|
||||
addEvents(Direction::Up);
|
||||
}
|
||||
checkPreloadMore();
|
||||
if (scrolledUp) {
|
||||
_scrollDateCheck.call();
|
||||
} else {
|
||||
|
@ -465,17 +460,17 @@ void InnerWidget::repaintScrollDateCallback() {
|
|||
update(0, updateTop, width(), updateHeight);
|
||||
}
|
||||
|
||||
void InnerWidget::checkPreloadMore() {
|
||||
if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) {
|
||||
preloadMore(Direction::Down);
|
||||
}
|
||||
if (_visibleTop < PreloadHeightsCount * (_visibleBottom - _visibleTop)) {
|
||||
preloadMore(Direction::Up);
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::updateEmptyText() {
|
||||
auto hasSearch = false;
|
||||
auto hasFilter = false;
|
||||
auto text = Ui::Text::Semibold((hasSearch || hasFilter)
|
||||
? tr::lng_admin_log_no_results_title(tr::now)
|
||||
: tr::lng_admin_log_no_events_title(tr::now));
|
||||
auto description = _peer->isMegagroup()
|
||||
? tr::lng_admin_log_no_events_text(tr::now)
|
||||
: tr::lng_admin_log_no_events_text_channel(tr::now);
|
||||
text.text.append(u"\n\n"_q + description);
|
||||
_emptyText.setMarkedText(st::defaultTextStyle, text);
|
||||
_emptyText.setMarkedText(st::defaultTextStyle, Ui::Text::Semibold(tr::lng_search_messages_none(tr::now)));
|
||||
}
|
||||
|
||||
QString InnerWidget::tooltipText() const {
|
||||
|
@ -613,7 +608,7 @@ void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
|||
|
||||
void InnerWidget::elementStartPremium(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
HistoryView::Element *replacing) {
|
||||
HistoryView::Element *replacing) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementCancelPremium(not_null<const Element*> view) {
|
||||
|
@ -633,17 +628,15 @@ bool InnerWidget::elementHideTopicButton(not_null<const Element*> view) {
|
|||
}
|
||||
|
||||
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
||||
if (!_filterChanged) {
|
||||
for (auto &item : _items) {
|
||||
item.clearView();
|
||||
}
|
||||
memento->setItems(
|
||||
base::take(_items),
|
||||
base::take(_eventIds),
|
||||
_upLoaded,
|
||||
_downLoaded);
|
||||
base::take(_itemsByData);
|
||||
for (auto &item : _items) {
|
||||
item.clearView();
|
||||
}
|
||||
memento->setItems(
|
||||
base::take(_items),
|
||||
base::take(_messageIds),
|
||||
_upLoaded,
|
||||
_downLoaded);
|
||||
base::take(_itemsByData);
|
||||
_upLoaded = _downLoaded = true; // Don't load or handle anything anymore.
|
||||
}
|
||||
|
||||
|
@ -653,23 +646,62 @@ void InnerWidget::restoreState(not_null<SectionMemento*> memento) {
|
|||
item.refreshView(this);
|
||||
_itemsByData.emplace(item->data(), item.get());
|
||||
}
|
||||
_eventIds = memento->takeEventIds();
|
||||
_messageIds = memento->takeMessageIds();
|
||||
_upLoaded = memento->upLoaded();
|
||||
_downLoaded = memento->downLoaded();
|
||||
_filterChanged = false;
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void InnerWidget::addEvents(Direction direction) {
|
||||
auto messages = AyuMessages::getEditedMessages(_item);
|
||||
if (messages.empty()) {
|
||||
void InnerWidget::preloadMore(Direction direction) {
|
||||
auto &loadedFlag = (direction == Direction::Up) ? _upLoaded : _downLoaded;
|
||||
if (loadedFlag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size = messages.size();
|
||||
auto &container = _items;
|
||||
auto maxId = (direction == Direction::Up) ? _minId : 0;
|
||||
auto minId = (direction == Direction::Up) ? 0 : _maxId;
|
||||
auto perPage = _items.empty() ? kMessagesFirstPage : kMessagesPerPage;
|
||||
|
||||
std::vector<AyuMessageBase> messages;
|
||||
if (_item) {
|
||||
// viewing edited history
|
||||
messages = AyuMessages::getEditedMessages(_item, minId, maxId, perPage);
|
||||
} else {
|
||||
// viewing deleted messages
|
||||
messages = AyuMessages::getDeletedMessages(_peer, minId, maxId, perPage);
|
||||
}
|
||||
|
||||
crl::on_main([=]
|
||||
{
|
||||
addMessages(direction, messages);
|
||||
});
|
||||
}
|
||||
|
||||
void InnerWidget::addMessages(Direction direction, const std::vector<AyuMessageBase> &messages) {
|
||||
auto up = (direction == Direction::Up);
|
||||
if (messages.empty()) {
|
||||
(up ? _upLoaded : _downLoaded) = true;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
// When loading items up we just add them to the back of the _items vector.
|
||||
// When loading items down we add them to a new vector and copy _items after them.
|
||||
auto newItemsForDownDirection = std::vector<OwnedItem>();
|
||||
auto oldItemsCount = _items.size();
|
||||
auto &addToItems = (direction == Direction::Up)
|
||||
? _items
|
||||
: newItemsForDownDirection;
|
||||
addToItems.reserve(oldItemsCount + messages.size() * 2);
|
||||
|
||||
for (const auto &message : messages) {
|
||||
const auto id = _item
|
||||
? message.fakeId // viewing edited history
|
||||
: message.messageId; // viewing deleted messages
|
||||
if (_messageIds.find(id) != _messageIds.end()) {
|
||||
return;
|
||||
}
|
||||
auto count = 0;
|
||||
const auto addOne = [&](
|
||||
OwnedItem item,
|
||||
TimeId sentDate,
|
||||
|
@ -678,19 +710,50 @@ void InnerWidget::addEvents(Direction direction) {
|
|||
if (sentDate) {
|
||||
_itemDates.emplace(item->data(), sentDate);
|
||||
}
|
||||
_messageIds.emplace(id);
|
||||
_itemsByData.emplace(item->data(), item.get());
|
||||
container.push_back(std::move(item));
|
||||
addToItems.push_back(std::move(item));
|
||||
++count;
|
||||
};
|
||||
GenerateItems(
|
||||
this,
|
||||
_history,
|
||||
message,
|
||||
addOne);
|
||||
if (count > 1) {
|
||||
// Reverse the inner order of the added messages, because we load messages
|
||||
// from bottom to top but inside one message they go from top to bottom.
|
||||
auto full = addToItems.size();
|
||||
auto from = full - count;
|
||||
for (auto i = 0, toReverse = count / 2; i != toReverse; ++i) {
|
||||
std::swap(addToItems[from + i], addToItems[full - i - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto newItemsCount = _items.size() + ((direction == Direction::Up) ? 0 : newItemsForDownDirection.size());
|
||||
if (newItemsCount != oldItemsCount) {
|
||||
if (direction == Direction::Down) {
|
||||
for (auto &item : _items) {
|
||||
newItemsForDownDirection.push_back(std::move(item));
|
||||
}
|
||||
_items = std::move(newItemsForDownDirection);
|
||||
}
|
||||
updateMinMaxIds();
|
||||
itemsAdded(direction, newItemsCount - oldItemsCount);
|
||||
}
|
||||
|
||||
itemsAdded(direction, size);
|
||||
update();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void InnerWidget::updateMinMaxIds() {
|
||||
if (_messageIds.empty()) {
|
||||
_maxId = _minId = 0;
|
||||
} else {
|
||||
_maxId = *_messageIds.rbegin();
|
||||
_minId = *_messageIds.begin();
|
||||
if (_minId == 1) {
|
||||
_upLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::itemsAdded(Direction direction, int addedCount) {
|
||||
|
@ -720,6 +783,7 @@ void InnerWidget::updateSize() {
|
|||
TWidget::resizeToWidth(width());
|
||||
restoreScrollPosition();
|
||||
updateVisibleTopItem();
|
||||
checkPreloadMore();
|
||||
}
|
||||
|
||||
int InnerWidget::resizeGetHeight(int newWidth) {
|
||||
|
@ -1636,4 +1700,4 @@ QPoint InnerWidget::mapPointToItem(QPoint point, const Element *view) const {
|
|||
|
||||
InnerWidget::~InnerWidget() = default;
|
||||
|
||||
} // namespace EditedLog
|
||||
} // namespace MessageHistory
|
|
@ -6,8 +6,8 @@
|
|||
// Copyright @Radolyn, 2024
|
||||
#pragma once
|
||||
|
||||
#include "ayu/ui/sections/edited/edited_log_item.h"
|
||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
||||
#include "ayu/ui/message_history/history_item.h"
|
||||
#include "ayu/ui/message_history/history_section.h"
|
||||
#include "base/timer.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "menu/menu_antispam_validator.h"
|
||||
|
@ -40,7 +40,7 @@ namespace Window {
|
|||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace EditedLog {
|
||||
namespace MessageHistory {
|
||||
|
||||
class SectionMemento;
|
||||
|
||||
|
@ -52,7 +52,7 @@ public:
|
|||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item);
|
||||
HistoryItem *item);
|
||||
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
|
@ -62,7 +62,7 @@ public:
|
|||
|
||||
[[nodiscard]] rpl::producer<int> scrollToSignal() const;
|
||||
|
||||
[[nodiscard]] not_null<PeerData*> channel() const {
|
||||
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
|
@ -207,15 +207,18 @@ private:
|
|||
TextForMimeData getSelectedText() const;
|
||||
|
||||
void updateVisibleTopItem();
|
||||
void preloadMore(Direction direction);
|
||||
void itemsAdded(Direction direction, int addedCount);
|
||||
void updateSize();
|
||||
void updateMinMaxIds();
|
||||
void updateEmptyText();
|
||||
void paintEmpty(Painter &p, not_null<const Ui::ChatStyle*> st);
|
||||
void addEvents(Direction direction);
|
||||
void addMessages(Direction direction, const std::vector<AyuMessageBase> &messages);
|
||||
Element *viewForItem(const HistoryItem *item);
|
||||
|
||||
void toggleScrollDateShown();
|
||||
void repaintScrollDateCallback();
|
||||
void checkPreloadMore();
|
||||
bool displayScrollDate() const;
|
||||
void scrollDateHide();
|
||||
void scrollDateCheck();
|
||||
|
@ -247,7 +250,7 @@ private:
|
|||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<HistoryItem*> _item;
|
||||
HistoryItem *_item;
|
||||
const not_null<History*> _history;
|
||||
MTP::Sender _api;
|
||||
|
||||
|
@ -255,7 +258,7 @@ private:
|
|||
std::shared_ptr<Ui::ChatTheme> _theme;
|
||||
|
||||
std::vector<OwnedItem> _items;
|
||||
std::set<uint64> _eventIds;
|
||||
std::set<uint64> _messageIds;
|
||||
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
|
||||
base::flat_map<not_null<const HistoryItem*>, TimeId> _itemDates;
|
||||
base::flat_set<FullMsgId> _animatedStickersPlayed;
|
||||
|
@ -279,10 +282,13 @@ private:
|
|||
Element *_scrollDateLastItem = nullptr;
|
||||
int _scrollDateLastItemTop = 0;
|
||||
|
||||
// Up - max, Down - min.
|
||||
uint64 _maxId = 0;
|
||||
uint64 _minId = 0;
|
||||
|
||||
// Don't load anything until the memento was read.
|
||||
bool _upLoaded = true;
|
||||
bool _downLoaded = true;
|
||||
bool _filterChanged = false;
|
||||
Ui::Text::String _emptyText;
|
||||
|
||||
MouseAction _mouseAction = MouseAction::None;
|
||||
|
@ -308,4 +314,4 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace EditedLog
|
||||
} // namespace MessageHistory
|
116
Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp
Normal file
116
Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
// 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, 2024
|
||||
#include "ayu/ui/message_history/history_item.h"
|
||||
#include "ayu/data/entities.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "ayu/ui/message_history/history_inner.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
namespace MessageHistory {
|
||||
|
||||
OwnedItem::OwnedItem(std::nullptr_t) {
|
||||
}
|
||||
|
||||
OwnedItem::OwnedItem(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<HistoryItem*> data)
|
||||
: _data(data), _view(_data->createView(delegate)) {
|
||||
}
|
||||
|
||||
OwnedItem::OwnedItem(OwnedItem &&other)
|
||||
: _data(base::take(other._data)), _view(base::take(other._view)) {
|
||||
}
|
||||
|
||||
OwnedItem &OwnedItem::operator=(OwnedItem &&other) {
|
||||
_data = base::take(other._data);
|
||||
_view = base::take(other._view);
|
||||
return *this;
|
||||
}
|
||||
|
||||
OwnedItem::~OwnedItem() {
|
||||
clearView();
|
||||
if (_data) {
|
||||
_data->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void OwnedItem::refreshView(
|
||||
not_null<HistoryView::ElementDelegate*> delegate) {
|
||||
_view = _data->createView(delegate);
|
||||
}
|
||||
|
||||
void OwnedItem::clearView() {
|
||||
_view = nullptr;
|
||||
}
|
||||
|
||||
void GenerateItems(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
AyuMessageBase message,
|
||||
Fn<void(OwnedItem item, TimeId sentDate, MsgId)> callback) {
|
||||
PeerData *from = history->owner().userLoaded(message.fromId);
|
||||
if (!from) {
|
||||
from = history->owner().channelLoaded(message.fromId);
|
||||
}
|
||||
if (!from) {
|
||||
from = reinterpret_cast<PeerData*>(history->owner().chatLoaded(message.fromId));
|
||||
}
|
||||
if (!from) {
|
||||
return;
|
||||
}
|
||||
const auto date = message.entityCreateDate;
|
||||
const auto addPart = [&](
|
||||
not_null<HistoryItem*> item,
|
||||
TimeId sentDate = 0,
|
||||
MsgId realId = MsgId())
|
||||
{
|
||||
return callback(OwnedItem(delegate, item), sentDate, realId);
|
||||
};
|
||||
|
||||
const auto fromName = from->name();
|
||||
const auto fromLink = from->createOpenLink();
|
||||
const auto fromLinkText = Ui::Text::Link(fromName, QString());
|
||||
|
||||
const auto makeSimpleTextMessage = [&](TextWithEntities &&text)
|
||||
{
|
||||
return history->makeMessage({
|
||||
.id = history->nextNonHistoryEntryId(),
|
||||
.flags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry,
|
||||
.from = from->id,
|
||||
.date = date,
|
||||
},
|
||||
std::move(text),
|
||||
MTP_messageMediaEmpty());
|
||||
};
|
||||
|
||||
const auto addSimpleTextMessage = [&](TextWithEntities &&text)
|
||||
{
|
||||
addPart(makeSimpleTextMessage(std::move(text)));
|
||||
};
|
||||
|
||||
const auto text = QString::fromStdString(message.text);
|
||||
addSimpleTextMessage(Ui::Text::WithEntities(text));
|
||||
}
|
||||
|
||||
} // namespace MessageHistory
|
|
@ -15,14 +15,14 @@ class ElementDelegate;
|
|||
class Element;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace EditedLog {
|
||||
namespace MessageHistory {
|
||||
|
||||
class OwnedItem;
|
||||
|
||||
void GenerateItems(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
EditedMessage message,
|
||||
AyuMessageBase message,
|
||||
Fn<void(OwnedItem item, TimeId sentDate, MsgId)> callback);
|
||||
|
||||
// Smart pointer wrapper for HistoryItem* that destroys the owned item.
|
||||
|
@ -60,4 +60,4 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace EditedLog
|
||||
} // namespace MessageHistory
|
|
@ -4,10 +4,10 @@
|
|||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2024
|
||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
||||
#include "ayu/ui/message_history/history_section.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "ayu/ui/sections/edited/edited_log_inner.h"
|
||||
#include "ayu/ui/message_history/history_inner.h"
|
||||
#include "base/timer.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
|
@ -25,7 +25,7 @@
|
|||
#include "window/window_session_controller.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
|
||||
namespace EditedLog {
|
||||
namespace MessageHistory {
|
||||
|
||||
class FixedBar final : public TWidget
|
||||
{
|
||||
|
@ -142,7 +142,7 @@ Widget::Widget(
|
|||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item)
|
||||
HistoryItem *item)
|
||||
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(peer)),
|
||||
_scroll(this, st::historyScroll, false),
|
||||
_fixedBar(this, controller, peer),
|
||||
|
@ -190,7 +190,7 @@ void Widget::updateAdaptiveLayout() {
|
|||
}
|
||||
|
||||
not_null<PeerData*> Widget::channel() const {
|
||||
return _inner->channel();
|
||||
return _inner->peer();
|
||||
}
|
||||
|
||||
Dialogs::RowDescriptor Widget::activeChat() const {
|
||||
|
@ -321,4 +321,4 @@ QRect Widget::floatPlayerAvailableRect() {
|
|||
return mapToGlobal(_scroll->geometry());
|
||||
}
|
||||
|
||||
} // namespace EditedLog
|
||||
} // namespace MessageHistory
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "window/section_widget.h"
|
||||
#include "window/section_memento.h"
|
||||
#include "ayu/ui/sections/edited/edited_log_item.h"
|
||||
#include "ayu/ui/message_history/history_item.h"
|
||||
#include "mtproto/sender.h"
|
||||
// don't reformat includes above
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace Profile {
|
|||
class BackButton;
|
||||
} // namespace Profile
|
||||
|
||||
namespace EditedLog {
|
||||
namespace MessageHistory {
|
||||
|
||||
class FixedBar;
|
||||
class InnerWidget;
|
||||
|
@ -35,7 +35,7 @@ public:
|
|||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item);
|
||||
HistoryItem *item);
|
||||
|
||||
not_null<PeerData*> channel() const;
|
||||
Dialogs::RowDescriptor activeChat() const override;
|
||||
|
@ -77,7 +77,7 @@ private:
|
|||
QPointer<InnerWidget> _inner;
|
||||
object_ptr<FixedBar> _fixedBar;
|
||||
object_ptr<Ui::PlainShadow> _fixedBarShadow;
|
||||
not_null<HistoryItem*> _item;
|
||||
HistoryItem *_item;
|
||||
|
||||
};
|
||||
|
||||
|
@ -91,6 +91,11 @@ public:
|
|||
_item(item) {
|
||||
}
|
||||
|
||||
SectionMemento(not_null<PeerData*> peer)
|
||||
: _peer(peer),
|
||||
_item(nullptr) {
|
||||
}
|
||||
|
||||
object_ptr<Window::SectionWidget> createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
@ -124,7 +129,7 @@ public:
|
|||
return std::move(_items);
|
||||
}
|
||||
|
||||
std::set<uint64> takeEventIds() {
|
||||
std::set<uint64> takeMessageIds() {
|
||||
return std::move(_eventIds);
|
||||
}
|
||||
|
||||
|
@ -138,14 +143,12 @@ public:
|
|||
|
||||
private:
|
||||
not_null<PeerData*> _peer;
|
||||
not_null<HistoryItem*> _item;
|
||||
HistoryItem *_item;
|
||||
int _scrollTop = 0;
|
||||
std::vector<not_null<UserData*>> _admins;
|
||||
std::vector<not_null<UserData*>> _adminsCanEdit;
|
||||
std::vector<OwnedItem> _items;
|
||||
std::set<uint64> _eventIds;
|
||||
bool _upLoaded = false;
|
||||
bool _downLoaded = true;
|
||||
};
|
||||
|
||||
} // namespace EditedLog
|
||||
} // namespace MessageHistory
|
|
@ -1,659 +0,0 @@
|
|||
// 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, 2024
|
||||
#include "ayu/ui/sections/edited/edited_log_item.h"
|
||||
#include "ayu/data/entities.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "ayu/ui/sections/edited/edited_log_inner.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
namespace EditedLog {
|
||||
namespace {
|
||||
|
||||
const auto CollectChanges = [](
|
||||
auto &phraseMap,
|
||||
auto plusFlags,
|
||||
auto minusFlags)
|
||||
{
|
||||
auto withPrefix = [&phraseMap](auto flags, QChar prefix)
|
||||
{
|
||||
auto result = QString();
|
||||
for (auto &phrase : phraseMap) {
|
||||
if (flags & phrase.first) {
|
||||
result.append('\n' + (prefix + phrase.second(tr::now)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto kMinus = QChar(0x2212);
|
||||
return withPrefix(plusFlags & ~minusFlags, '+')
|
||||
+ withPrefix(minusFlags & ~plusFlags, kMinus);
|
||||
};
|
||||
|
||||
TextWithEntities GenerateAdminChangeText(
|
||||
not_null<ChannelData*> channel,
|
||||
const TextWithEntities &user,
|
||||
ChatAdminRightsInfo newRights,
|
||||
ChatAdminRightsInfo prevRights) {
|
||||
using Flag = ChatAdminRight;
|
||||
using Flags = ChatAdminRights;
|
||||
|
||||
auto result = tr::lng_admin_log_promoted(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user,
|
||||
Ui::Text::WithEntities);
|
||||
|
||||
const auto useInviteLinkPhrase = channel->isMegagroup()
|
||||
&& channel->anyoneCanAddMembers();
|
||||
const auto invitePhrase = useInviteLinkPhrase
|
||||
? tr::lng_admin_log_admin_invite_link
|
||||
: tr::lng_admin_log_admin_invite_users;
|
||||
const auto callPhrase = channel->isBroadcast()
|
||||
? tr::lng_admin_log_admin_manage_calls_channel
|
||||
: tr::lng_admin_log_admin_manage_calls;
|
||||
static auto phraseMap = std::map<Flags, tr::phrase<>>{
|
||||
{Flag::ChangeInfo, tr::lng_admin_log_admin_change_info},
|
||||
{Flag::PostMessages, tr::lng_admin_log_admin_post_messages},
|
||||
{Flag::EditMessages, tr::lng_admin_log_admin_edit_messages},
|
||||
{Flag::DeleteMessages, tr::lng_admin_log_admin_delete_messages},
|
||||
{Flag::BanUsers, tr::lng_admin_log_admin_ban_users},
|
||||
{Flag::InviteByLinkOrAdd, invitePhrase},
|
||||
{Flag::ManageTopics, tr::lng_admin_log_admin_manage_topics},
|
||||
{Flag::PinMessages, tr::lng_admin_log_admin_pin_messages},
|
||||
{Flag::ManageCall, tr::lng_admin_log_admin_manage_calls},
|
||||
{Flag::AddAdmins, tr::lng_admin_log_admin_add_admins},
|
||||
{Flag::Anonymous, tr::lng_admin_log_admin_remain_anonymous},
|
||||
};
|
||||
phraseMap[Flag::InviteByLinkOrAdd] = invitePhrase;
|
||||
phraseMap[Flag::ManageCall] = callPhrase;
|
||||
|
||||
if (!channel->isMegagroup()) {
|
||||
// Don't display "Ban users" changes in channels.
|
||||
newRights.flags &= ~Flag::BanUsers;
|
||||
prevRights.flags &= ~Flag::BanUsers;
|
||||
}
|
||||
|
||||
const auto changes = CollectChanges(
|
||||
phraseMap,
|
||||
newRights.flags,
|
||||
prevRights.flags);
|
||||
if (!changes.isEmpty()) {
|
||||
result.text.append('\n' + changes);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
QString GeneratePermissionsChangeText(
|
||||
ChatRestrictionsInfo newRights,
|
||||
ChatRestrictionsInfo prevRights) {
|
||||
using Flag = ChatRestriction;
|
||||
using Flags = ChatRestrictions;
|
||||
|
||||
static auto phraseMap = std::map<Flags, tr::phrase<>>{
|
||||
{Flag::ViewMessages, tr::lng_admin_log_banned_view_messages},
|
||||
{Flag::SendOther, tr::lng_admin_log_banned_send_messages},
|
||||
{Flag::SendPhotos, tr::lng_admin_log_banned_send_photos},
|
||||
{Flag::SendVideos, tr::lng_admin_log_banned_send_videos},
|
||||
{Flag::SendMusic, tr::lng_admin_log_banned_send_music},
|
||||
{Flag::SendFiles, tr::lng_admin_log_banned_send_files},
|
||||
{
|
||||
Flag::SendVoiceMessages,
|
||||
tr::lng_admin_log_banned_send_voice_messages
|
||||
},
|
||||
{
|
||||
Flag::SendVideoMessages,
|
||||
tr::lng_admin_log_banned_send_video_messages
|
||||
},
|
||||
{
|
||||
Flag::SendStickers
|
||||
| Flag::SendGifs
|
||||
| Flag::SendInline
|
||||
| Flag::SendGames,
|
||||
tr::lng_admin_log_banned_send_stickers
|
||||
},
|
||||
{Flag::EmbedLinks, tr::lng_admin_log_banned_embed_links},
|
||||
{Flag::SendPolls, tr::lng_admin_log_banned_send_polls},
|
||||
{Flag::ChangeInfo, tr::lng_admin_log_admin_change_info},
|
||||
{Flag::AddParticipants, tr::lng_admin_log_admin_invite_users},
|
||||
{Flag::CreateTopics, tr::lng_admin_log_admin_create_topics},
|
||||
{Flag::PinMessages, tr::lng_admin_log_admin_pin_messages},
|
||||
};
|
||||
return CollectChanges(phraseMap, prevRights.flags, newRights.flags);
|
||||
}
|
||||
|
||||
TextWithEntities GeneratePermissionsChangeText(
|
||||
PeerId participantId,
|
||||
const TextWithEntities &user,
|
||||
ChatRestrictionsInfo newRights,
|
||||
ChatRestrictionsInfo prevRights) {
|
||||
using Flag = ChatRestriction;
|
||||
|
||||
const auto newFlags = newRights.flags;
|
||||
const auto newUntil = newRights.until;
|
||||
const auto prevFlags = prevRights.flags;
|
||||
const auto indefinitely = ChannelData::IsRestrictedForever(newUntil);
|
||||
if (newFlags & Flag::ViewMessages) {
|
||||
return tr::lng_admin_log_banned(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user,
|
||||
Ui::Text::WithEntities);
|
||||
} else if (newFlags == 0
|
||||
&& (prevFlags & Flag::ViewMessages)
|
||||
&& !peerIsUser(participantId)) {
|
||||
return tr::lng_admin_log_unbanned(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
const auto untilText = indefinitely
|
||||
? tr::lng_admin_log_restricted_forever(tr::now)
|
||||
: tr::lng_admin_log_restricted_until(
|
||||
tr::now,
|
||||
lt_date,
|
||||
langDateTime(base::unixtime::parse(newUntil)));
|
||||
auto result = tr::lng_admin_log_restricted(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user,
|
||||
lt_until,
|
||||
TextWithEntities{untilText},
|
||||
Ui::Text::WithEntities);
|
||||
const auto changes = GeneratePermissionsChangeText(newRights, prevRights);
|
||||
if (!changes.isEmpty()) {
|
||||
result.text.append('\n' + changes);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString PublicJoinLink() {
|
||||
return u"(public_join_link)"_q;
|
||||
}
|
||||
|
||||
QString ExtractInviteLink(const MTPExportedChatInvite &data) {
|
||||
return data.match([&](const MTPDchatInviteExported &data)
|
||||
{
|
||||
return qs(data.vlink());
|
||||
},
|
||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
||||
{
|
||||
return PublicJoinLink();
|
||||
});
|
||||
}
|
||||
|
||||
QString ExtractInviteLinkLabel(const MTPExportedChatInvite &data) {
|
||||
return data.match([&](const MTPDchatInviteExported &data)
|
||||
{
|
||||
return qs(data.vtitle().value_or_empty());
|
||||
},
|
||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
||||
{
|
||||
return PublicJoinLink();
|
||||
});
|
||||
}
|
||||
|
||||
QString InternalInviteLinkUrl(const MTPExportedChatInvite &data) {
|
||||
const auto base64 = ExtractInviteLink(data).toUtf8().toBase64();
|
||||
return "internal:show_invite_link/?link=" + QString::fromLatin1(base64);
|
||||
}
|
||||
|
||||
QString GenerateInviteLinkText(const MTPExportedChatInvite &data) {
|
||||
const auto label = ExtractInviteLinkLabel(data);
|
||||
return label.isEmpty()
|
||||
? ExtractInviteLink(data).replace(
|
||||
u"https://"_q,
|
||||
QString()
|
||||
).replace(
|
||||
u"t.me/joinchat/"_q,
|
||||
QString()
|
||||
)
|
||||
: label;
|
||||
}
|
||||
|
||||
TextWithEntities GenerateInviteLinkLink(const MTPExportedChatInvite &data) {
|
||||
const auto text = GenerateInviteLinkText(data);
|
||||
return text.endsWith(Ui::kQEllipsis)
|
||||
? TextWithEntities{.text = text}
|
||||
: Ui::Text::Link(text, InternalInviteLinkUrl(data));
|
||||
}
|
||||
|
||||
TextWithEntities GenerateInviteLinkChangeText(
|
||||
const MTPExportedChatInvite &newLink,
|
||||
const MTPExportedChatInvite &prevLink) {
|
||||
auto link = TextWithEntities{GenerateInviteLinkText(newLink)};
|
||||
if (!link.text.endsWith(Ui::kQEllipsis)) {
|
||||
link.entities.push_back({
|
||||
EntityType::CustomUrl,
|
||||
0,
|
||||
int(link.text.size()),
|
||||
InternalInviteLinkUrl(newLink)
|
||||
});
|
||||
}
|
||||
auto result = tr::lng_admin_log_edited_invite_link(
|
||||
tr::now,
|
||||
lt_link,
|
||||
link,
|
||||
Ui::Text::WithEntities);
|
||||
result.text.append('\n');
|
||||
|
||||
const auto label = [](const MTPExportedChatInvite &link)
|
||||
{
|
||||
return link.match([](const MTPDchatInviteExported &data)
|
||||
{
|
||||
return qs(data.vtitle().value_or_empty());
|
||||
},
|
||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
||||
{
|
||||
return PublicJoinLink();
|
||||
});
|
||||
};
|
||||
const auto expireDate = [](const MTPExportedChatInvite &link)
|
||||
{
|
||||
return link.match([](const MTPDchatInviteExported &data)
|
||||
{
|
||||
return data.vexpire_date().value_or_empty();
|
||||
},
|
||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
||||
{
|
||||
return TimeId();
|
||||
});
|
||||
};
|
||||
const auto usageLimit = [](const MTPExportedChatInvite &link)
|
||||
{
|
||||
return link.match([](const MTPDchatInviteExported &data)
|
||||
{
|
||||
return data.vusage_limit().value_or_empty();
|
||||
},
|
||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
||||
{
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
const auto requestApproval = [](const MTPExportedChatInvite &link)
|
||||
{
|
||||
return link.match([](const MTPDchatInviteExported &data)
|
||||
{
|
||||
return data.is_request_needed();
|
||||
},
|
||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
||||
{
|
||||
return true;
|
||||
});
|
||||
};
|
||||
const auto wrapDate = [](TimeId date)
|
||||
{
|
||||
return date
|
||||
? langDateTime(base::unixtime::parse(date))
|
||||
: tr::lng_group_invite_expire_never(tr::now);
|
||||
};
|
||||
const auto wrapUsage = [](int count)
|
||||
{
|
||||
return count
|
||||
? QString::number(count)
|
||||
: tr::lng_group_invite_usage_any(tr::now);
|
||||
};
|
||||
const auto wasLabel = label(prevLink);
|
||||
const auto nowLabel = label(newLink);
|
||||
const auto wasExpireDate = expireDate(prevLink);
|
||||
const auto nowExpireDate = expireDate(newLink);
|
||||
const auto wasUsageLimit = usageLimit(prevLink);
|
||||
const auto nowUsageLimit = usageLimit(newLink);
|
||||
const auto wasRequestApproval = requestApproval(prevLink);
|
||||
const auto nowRequestApproval = requestApproval(newLink);
|
||||
if (wasLabel != nowLabel) {
|
||||
result.text.append('\n').append(
|
||||
tr::lng_admin_log_invite_link_label(
|
||||
tr::now,
|
||||
lt_previous,
|
||||
wasLabel,
|
||||
lt_limit,
|
||||
nowLabel));
|
||||
}
|
||||
if (wasExpireDate != nowExpireDate) {
|
||||
result.text.append('\n').append(
|
||||
tr::lng_admin_log_invite_link_expire_date(
|
||||
tr::now,
|
||||
lt_previous,
|
||||
wrapDate(wasExpireDate),
|
||||
lt_limit,
|
||||
wrapDate(nowExpireDate)));
|
||||
}
|
||||
if (wasUsageLimit != nowUsageLimit) {
|
||||
result.text.append('\n').append(
|
||||
tr::lng_admin_log_invite_link_usage_limit(
|
||||
tr::now,
|
||||
lt_previous,
|
||||
wrapUsage(wasUsageLimit),
|
||||
lt_limit,
|
||||
wrapUsage(nowUsageLimit)));
|
||||
}
|
||||
if (wasRequestApproval != nowRequestApproval) {
|
||||
result.text.append('\n').append(
|
||||
nowRequestApproval
|
||||
? tr::lng_admin_log_invite_link_request_needed(tr::now)
|
||||
: tr::lng_admin_log_invite_link_request_not_needed(tr::now));
|
||||
}
|
||||
|
||||
result.entities.push_front(
|
||||
EntityInText(EntityType::Italic, 0, result.text.size()));
|
||||
return result;
|
||||
};
|
||||
|
||||
auto GenerateParticipantString(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId participantId) {
|
||||
// User name in "User name (@username)" format with entities.
|
||||
const auto peer = session->data().peer(participantId);
|
||||
auto name = TextWithEntities{peer->name()};
|
||||
if (const auto user = peer->asUser()) {
|
||||
const auto data = TextUtilities::MentionNameDataFromFields({
|
||||
.selfId = session->userId().bare,
|
||||
.userId = peerToUser(user->id).bare,
|
||||
.accessHash = user->accessHash(),
|
||||
});
|
||||
name.entities.push_back({
|
||||
EntityType::MentionName,
|
||||
0,
|
||||
int(name.text.size()),
|
||||
data,
|
||||
});
|
||||
}
|
||||
const auto username = peer->username();
|
||||
if (username.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
auto mention = TextWithEntities{'@' + username};
|
||||
mention.entities.push_back({
|
||||
EntityType::Mention,
|
||||
0,
|
||||
int(mention.text.size())
|
||||
});
|
||||
return tr::lng_admin_log_user_with_username(
|
||||
tr::now,
|
||||
lt_name,
|
||||
name,
|
||||
lt_mention,
|
||||
mention,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
auto GenerateParticipantChangeText(
|
||||
not_null<ChannelData*> channel,
|
||||
const Api::ChatParticipant &participant,
|
||||
std::optional<Api::ChatParticipant> oldParticipant = std::nullopt) {
|
||||
using Type = Api::ChatParticipant::Type;
|
||||
const auto oldRights = oldParticipant
|
||||
? oldParticipant->rights()
|
||||
: ChatAdminRightsInfo();
|
||||
const auto oldRestrictions = oldParticipant
|
||||
? oldParticipant->restrictions()
|
||||
: ChatRestrictionsInfo();
|
||||
|
||||
const auto generateOther = [&](PeerId participantId)
|
||||
{
|
||||
auto user = GenerateParticipantString(
|
||||
&channel->session(),
|
||||
participantId);
|
||||
if (oldParticipant && oldParticipant->type() == Type::Admin) {
|
||||
return GenerateAdminChangeText(
|
||||
channel,
|
||||
user,
|
||||
ChatAdminRightsInfo(),
|
||||
oldRights);
|
||||
} else if (oldParticipant && oldParticipant->type() == Type::Banned) {
|
||||
return GeneratePermissionsChangeText(
|
||||
participantId,
|
||||
user,
|
||||
ChatRestrictionsInfo(),
|
||||
oldRestrictions);
|
||||
} else if (oldParticipant
|
||||
&& oldParticipant->type() == Type::Restricted
|
||||
&& (participant.type() == Type::Member
|
||||
|| participant.type() == Type::Left)) {
|
||||
return GeneratePermissionsChangeText(
|
||||
participantId,
|
||||
user,
|
||||
ChatRestrictionsInfo(),
|
||||
oldRestrictions);
|
||||
}
|
||||
return tr::lng_admin_log_invited(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user,
|
||||
Ui::Text::WithEntities);
|
||||
};
|
||||
|
||||
auto result = [&]
|
||||
{
|
||||
const auto &peerId = participant.id();
|
||||
switch (participant.type()) {
|
||||
case Api::ChatParticipant::Type::Creator: {
|
||||
// No valid string here :(
|
||||
const auto user = GenerateParticipantString(
|
||||
&channel->session(),
|
||||
peerId);
|
||||
if (peerId == channel->session().userPeerId()) {
|
||||
return GenerateAdminChangeText(
|
||||
channel,
|
||||
user,
|
||||
participant.rights(),
|
||||
oldRights);
|
||||
}
|
||||
return tr::lng_admin_log_transferred(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
case Api::ChatParticipant::Type::Admin: {
|
||||
const auto user = GenerateParticipantString(
|
||||
&channel->session(),
|
||||
peerId);
|
||||
return GenerateAdminChangeText(
|
||||
channel,
|
||||
user,
|
||||
participant.rights(),
|
||||
oldRights);
|
||||
}
|
||||
case Api::ChatParticipant::Type::Restricted:
|
||||
case Api::ChatParticipant::Type::Banned: {
|
||||
const auto user = GenerateParticipantString(
|
||||
&channel->session(),
|
||||
peerId);
|
||||
return GeneratePermissionsChangeText(
|
||||
peerId,
|
||||
user,
|
||||
participant.restrictions(),
|
||||
oldRestrictions);
|
||||
}
|
||||
case Api::ChatParticipant::Type::Left:
|
||||
case Api::ChatParticipant::Type::Member: return generateOther(peerId);
|
||||
};
|
||||
Unexpected("Participant type in GenerateParticipantChangeText.");
|
||||
}();
|
||||
|
||||
result.entities.push_front(
|
||||
EntityInText(EntityType::Italic, 0, result.text.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
TextWithEntities GenerateParticipantChangeText(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPChannelParticipant &participant,
|
||||
std::optional<MTPChannelParticipant> oldParticipant = std::nullopt) {
|
||||
return GenerateParticipantChangeText(
|
||||
channel,
|
||||
Api::ChatParticipant(participant, channel),
|
||||
oldParticipant
|
||||
? std::make_optional(Api::ChatParticipant(
|
||||
*oldParticipant,
|
||||
channel))
|
||||
: std::nullopt);
|
||||
}
|
||||
|
||||
TextWithEntities GenerateDefaultBannedRightsChangeText(
|
||||
not_null<ChannelData*> channel,
|
||||
ChatRestrictionsInfo rights,
|
||||
ChatRestrictionsInfo oldRights) {
|
||||
auto result = TextWithEntities{
|
||||
tr::lng_admin_log_changed_default_permissions(tr::now)
|
||||
};
|
||||
const auto changes = GeneratePermissionsChangeText(rights, oldRights);
|
||||
if (!changes.isEmpty()) {
|
||||
result.text.append('\n' + changes);
|
||||
}
|
||||
result.entities.push_front(
|
||||
EntityInText(EntityType::Italic, 0, result.text.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsTopicClosed(const MTPForumTopic &topic) {
|
||||
return topic.match([](const MTPDforumTopic &data)
|
||||
{
|
||||
return data.is_closed();
|
||||
},
|
||||
[](const MTPDforumTopicDeleted &)
|
||||
{
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsTopicHidden(const MTPForumTopic &topic) {
|
||||
return topic.match([](const MTPDforumTopic &data)
|
||||
{
|
||||
return data.is_hidden();
|
||||
},
|
||||
[](const MTPDforumTopicDeleted &)
|
||||
{
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities GenerateTopicLink(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPForumTopic &topic) {
|
||||
return topic.match([&](const MTPDforumTopic &data)
|
||||
{
|
||||
return Ui::Text::Link(
|
||||
Data::ForumTopicIconWithTitle(
|
||||
data.vid().v,
|
||||
data.vicon_emoji_id().value_or_empty(),
|
||||
qs(data.vtitle())),
|
||||
u"internal:url:https://t.me/c/%1/%2"_q.arg(
|
||||
peerToChannel(channel->id).bare).arg(
|
||||
data.vid().v));
|
||||
},
|
||||
[](const MTPDforumTopicDeleted &)
|
||||
{
|
||||
return TextWithEntities{u"Deleted"_q};
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OwnedItem::OwnedItem(std::nullptr_t) {
|
||||
}
|
||||
|
||||
OwnedItem::OwnedItem(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<HistoryItem*> data)
|
||||
: _data(data), _view(_data->createView(delegate)) {
|
||||
}
|
||||
|
||||
OwnedItem::OwnedItem(OwnedItem &&other)
|
||||
: _data(base::take(other._data)), _view(base::take(other._view)) {
|
||||
}
|
||||
|
||||
OwnedItem &OwnedItem::operator=(OwnedItem &&other) {
|
||||
_data = base::take(other._data);
|
||||
_view = base::take(other._view);
|
||||
return *this;
|
||||
}
|
||||
|
||||
OwnedItem::~OwnedItem() {
|
||||
clearView();
|
||||
if (_data) {
|
||||
_data->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void OwnedItem::refreshView(
|
||||
not_null<HistoryView::ElementDelegate*> delegate) {
|
||||
_view = _data->createView(delegate);
|
||||
}
|
||||
|
||||
void OwnedItem::clearView() {
|
||||
_view = nullptr;
|
||||
}
|
||||
|
||||
void GenerateItems(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
EditedMessage message,
|
||||
Fn<void(OwnedItem item, TimeId sentDate, MsgId)> callback) {
|
||||
PeerData *from = history->owner().userLoaded(message.fromId);
|
||||
if (!from) {
|
||||
from = history->owner().channelLoaded(message.fromId);
|
||||
}
|
||||
if (!from) {
|
||||
from = reinterpret_cast<PeerData*>(history->owner().chatLoaded(message.fromId));
|
||||
}
|
||||
if (!from) {
|
||||
return;
|
||||
}
|
||||
const auto date = message.entityCreateDate;
|
||||
const auto addPart = [&](
|
||||
not_null<HistoryItem*> item,
|
||||
TimeId sentDate = 0,
|
||||
MsgId realId = MsgId())
|
||||
{
|
||||
return callback(OwnedItem(delegate, item), sentDate, realId);
|
||||
};
|
||||
|
||||
const auto fromName = from->name();
|
||||
const auto fromLink = from->createOpenLink();
|
||||
const auto fromLinkText = Ui::Text::Link(fromName, QString());
|
||||
|
||||
const auto makeSimpleTextMessage = [&](TextWithEntities &&text)
|
||||
{
|
||||
return history->makeMessage({
|
||||
.id = history->nextNonHistoryEntryId(),
|
||||
.flags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry,
|
||||
.from = from->id,
|
||||
.date = date,
|
||||
}, std::move(text), MTP_messageMediaEmpty());
|
||||
};
|
||||
|
||||
const auto addSimpleTextMessage = [&](TextWithEntities &&text)
|
||||
{
|
||||
addPart(makeSimpleTextMessage(std::move(text)));
|
||||
};
|
||||
|
||||
const auto text = QString::fromStdString(message.text);
|
||||
addSimpleTextMessage(Ui::Text::WithEntities(text));
|
||||
}
|
||||
|
||||
} // namespace EditedLog
|
|
@ -2549,23 +2549,25 @@ void Session::processMessagesDeleted(
|
|||
return;
|
||||
}
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
|
||||
auto historiesToCheck = base::flat_set<not_null<History*>>();
|
||||
for (const auto &messageId : data) {
|
||||
const auto i = list ? list->find(messageId.v) : Messages::iterator();
|
||||
if (list && i != list->end()) {
|
||||
const auto history = i->second->history();
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->saveDeletedMessages) {
|
||||
i->second->destroy();
|
||||
} else {
|
||||
i->second->setAyuHint(settings->deletedMark);
|
||||
AyuMessages::addDeletedMessage(i->second);
|
||||
}
|
||||
|
||||
if (!history->chatListMessageKnown()) {
|
||||
if (!history->chatListMessageKnown() && !settings->saveDeletedMessages) {
|
||||
historiesToCheck.emplace(history);
|
||||
}
|
||||
} else if (affected) {
|
||||
} else if (affected && !settings->saveDeletedMessages) {
|
||||
affected->unknownMessageDeleted(messageId.v);
|
||||
}
|
||||
}
|
||||
|
@ -2575,19 +2577,21 @@ void Session::processMessagesDeleted(
|
|||
}
|
||||
|
||||
void Session::processNonChannelMessagesDeleted(const QVector<MTPint> &data) {
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
|
||||
auto historiesToCheck = base::flat_set<not_null<History*>>();
|
||||
for (const auto &messageId : data) {
|
||||
if (const auto item = nonChannelMessage(messageId.v)) {
|
||||
const auto history = item->history();
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->saveDeletedMessages) {
|
||||
item->destroy();
|
||||
} else {
|
||||
item->setAyuHint(settings->deletedMark);
|
||||
AyuMessages::addDeletedMessage(item);
|
||||
}
|
||||
|
||||
if (!history->chatListMessageKnown()) {
|
||||
if (!history->chatListMessageKnown() && !settings->saveDeletedMessages) {
|
||||
historiesToCheck.emplace(history);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,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"
|
||||
|
||||
|
||||
namespace Window {
|
||||
|
@ -1505,6 +1506,7 @@ void Filler::fillHistoryActions() {
|
|||
addExportChat();
|
||||
addTranslate();
|
||||
addReport();
|
||||
AyuUi::AddDeletedMessagesActions(_peer, _controller, _request, _addAction);
|
||||
addClearHistory();
|
||||
addDeleteChat();
|
||||
addLeaveChat();
|
||||
|
|
Loading…
Add table
Reference in a new issue