Merge pull request 'ru strings + dynamic fast messages slots + ui streaming mode fix' (#2) from m3 into dev

Reviewed-on: #2
This commit is contained in:
devilreef 2026-05-04 14:18:46 +00:00
commit 77c9d561a9
13 changed files with 278 additions and 43 deletions

View file

@ -26,6 +26,10 @@ When you encounter a new symptom and resolve it, **append a new row** to the rel
| `'UserData::inputUser': non-standard syntax; use '&' to create a pointer to member` (C3867) | `inputUser` on `UserData` is a member function, not a field | Call it: `user->inputUser()`, not `user->inputUser`. |
| `C2039 'X' is not a member of 'Arcanegram::Config::Sync'` when the symbol lives in `Arcanegram::Sync` (sibling namespace) | From inside `namespace Arcanegram::Config`, unqualified `Sync::` resolves to the nested `Config::Sync` first, shadowing `Arcanegram::Sync` | Fully qualify: `::Arcanegram::Sync::X`. |
| `lambda declarator without a parameter list requires at least '/std:c++23preview'` (C5279) | C++23-only abbreviated lambda syntax: `[] -> Type { ... }` | Write the parameter list explicitly: `[]() -> Type { ... }`. |
| `LNK1181: cannot open input file '...\Release\<lib>.lib'` (qt, openssl, tg_owt, libavif, etc.) often paired with `LNK2038: mismatch detected for _ITERATOR_DEBUG_LEVEL '0' vs '2'` and `RuntimeLibrary 'MT_StaticRelease' vs 'MTd_StaticDebug'` when running `build-telegram.bat release` | `prepare-deps.bat` was last run with default `debug` (which passes `skip-release` to prepare.py) so Release halves of big libs were never built; CMake then tries to mix Release-config Telegram against Debug-only Qt plugin libs (`/MTd`, IDL=2) | Run `prepare-deps.bat both` (~30 min); the `skip-release` toggle invalidates affected stages' cache keys so prepare.py rebuilds them with both configs. Then retry `build-telegram.bat release`. |
| `C2027: использование неопределенного типа "Ui::SettingsButton"` / `C2039: 'setClickedCallback' is not a member of 'gsl::not_null<Settings::Button*>'` when calling `AddButtonWithIcon(...)->setClickedCallback(...)` | `settings_common.h` only forward-declares `Ui::SettingsButton`; the full definition (and `AbstractButton::setClickedCallback`) lives in `lib_ui` | Add `#include "ui/widgets/buttons.h"` to the TU. |
| `C2039: 'Id': is not a member of 'Lang'` / `C3861: 'Updated': identifier not found` in fork code using `::Lang::Id()` or `::Lang::Updated()` | These free functions are declared in `lang/lang_keys.h`, not `lang/lang_instance.h`. Including only `lang_instance.h` leaves them undeclared. | Add `#include "lang/lang_keys.h"` alongside `lang_instance.h`. Also `std::move` the `Updated()` producer before piping into `rpl::on_next`. |
## Runtime / behavior bugs

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
node_modules/
worktree/
issues/
MEMORY.md
.DS_Store
*.log
.pnpm-store/

View file

@ -0,0 +1,42 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: m3trika <x@iamsofuckinginsane.ru>
Date: Mon, 4 May 2026 16:21:17 +0400
Subject: [PATCH] Expose ag_loadContent on Lang::Instance for fork string
injection
---
Telegram/SourceFiles/lang/lang_instance.cpp | 5 +++++
Telegram/SourceFiles/lang/lang_instance.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp
index 0000000..0000000 100644
--- a/Telegram/SourceFiles/lang/lang_instance.cpp
+++ b/Telegram/SourceFiles/lang/lang_instance.cpp
@@ -559,6 +559,11 @@ void Instance::loadFromContent(const QByteArray &content) {
}
}
+void Instance::ag_loadContent(const QByteArray &content) {
+ loadFromContent(content);
+ _updated.fire({});
+}
+
void Instance::fillFromCustomContent(
const QString &absolutePath,
const QString &relativePath,
diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h
index 0000000..0000000 100644
--- a/Telegram/SourceFiles/lang/lang_instance.h
+++ b/Telegram/SourceFiles/lang/lang_instance.h
@@ -80,6 +80,7 @@ public:
void applyDifference(
Pack pack,
const MTPDlangPackDifference &difference);
+ void ag_loadContent(const QByteArray &content);
static std::map<ushort, QString> ParseStrings(
const MTPVector<MTPLangPackString> &strings);
--
2.52.0.windows.1

View file

@ -20,6 +20,8 @@
"ag_fast_messages_info" = "Type a message per slot. Bind keys in Settings → Chat Settings → Keyboard Shortcuts.";
"ag_fast_messages_slot" = "Slot {index}";
"ag_fast_messages_placeholder" = "Message text";
"ag_fast_messages_add" = "Add slot";
"ag_fast_messages_remove" = "Remove slot";
"ag_settings_appearance" = "Chat appearance";
"ag_settings_sync_title" = "Cloud sync";
"ag_sync_enabled_setting" = "Enable cloud sync";

View file

@ -0,0 +1,44 @@
"ag_settings_title" = "Arcanegram";
"ag_forwarded_show_date_setting" = "Показывать дату в пересланных сообщениях";
"ag_forwarded_show_date_info" = "Добавлять исходную дату отправки к строке «Переслано от».";
"ag_forwarded_date_label" = "Дата";
"ag_show_seconds_setting" = "Показывать секунды в метках времени";
"ag_show_seconds_info" = "Отображать ЧЧ:ММ:СС вместо ЧЧ:ММ в метках времени сообщений и подсказках.";
"ag_chat_wallpaper_disabled_setting" = "Игнорировать фон чата";
"ag_chat_wallpaper_disabled_info" = "Всегда использовать фон по умолчанию; игнорировать фон, установленный чатами и каналами.";
"ag_hidden_users_title" = "Скрытые пользователи";
"ag_hidden_users_info" = "Сообщения от этих пользователей скрыты во всех чатах.";
"ag_hidden_users_empty" = "Нет скрытых пользователей. Нажмите правой кнопкой на аватар, чтобы скрыть сообщения.";
"ag_hidden_users_menu_hide" = "Скрыть сообщения от этого пользователя";
"ag_hidden_users_menu_unhide" = "Показать сообщения";
"ag_hidden_user_message" = "Сообщение от скрытого пользователя";
"ag_hidden_user_name" = "Скрытый пользователь";
"ag_hidden_users_remove" = "Удалить";
"ag_peer_id_copy_botapi" = "Копировать ID (Bot API)";
"ag_peer_id_copy_mtproto" = "Копировать ID (MTProto)";
"ag_fast_messages_title" = "Быстрые сообщения";
"ag_fast_messages_info" = "Введите сообщение для каждого слота. Привяжите клавиши в Настройках → Настройки чата → Горячие клавиши.";
"ag_fast_messages_slot" = "Слот {index}";
"ag_fast_messages_placeholder" = "Текст сообщения";
"ag_fast_messages_add" = "Добавить слот";
"ag_fast_messages_remove" = "Удалить слот";
"lng_shortcuts_fast_message" = "Быстрое сообщение {index}";
"ag_settings_appearance" = "Внешний вид чата";
"ag_settings_sync_title" = "Облачная синхронизация";
"ag_sync_enabled_setting" = "Включить облачную синхронизацию";
"ag_sync_enabled_info" = "Синхронизировать настройки Arcanegram между устройствами через arcanesync.";
"ag_sync_endpoint_placeholder" = "URL сервера";
"ag_sync_status_disabled" = "Отключено";
"ag_sync_status_ready" = "Готово";
"ag_sync_status_synced" = "Синхронизировано";
"ag_sync_status_syncing" = "Синхронизация…";
"ag_sync_status_offline" = "Не в сети";
"ag_sync_status_offline_pending" = "Не в сети · {n} ожидают";
"ag_sync_status_conflict" = "Разрешение конфликта…";
"ag_streamer_title" = "Режим стримера";
"ag_streamer_subtitle" = "Скрывать имена, аватары и идентификаторы при демонстрации экрана.";
"ag_streamer_enabled" = "Анонимизировать других пользователей";
"ag_streamer_enabled_about" = "Заменяет имя каждого пользователя псевдонимом, убирает аватар, цвет и значки, скрывает телефон, имя пользователя, ID, описание, день рождения и время последнего визита.";
"ag_streamer_anonymize_bots" = "Анонимизировать ботов";
"ag_streamer_anonymize_bots_about" = "Боты открыты по умолчанию, так как обычно это намеренно. Включите, чтобы также давать им псевдонимы.";
"ag_streamer_preview" = "Пример: {name}";

1
series
View file

@ -38,5 +38,6 @@ feature/streamer-mode-search.patch
feature/streamer-mode-forum-topic.patch
feature/streamer-mode-rank.patch
feature/streamer-mode-seen-avatars.patch
misc/lang-ag-load-content.patch
feature/streamer-mode-rating.patch
misc/branding.patch

View file

@ -22,6 +22,8 @@ PRIVATE
ag_hooks.h
ag_hooks.cpp
ag_lang.h
ag_lang.cpp
ag_config.h
ag_config.cpp
ag_refresh.h

View file

@ -80,6 +80,16 @@ namespace FastMessages {
constexpr int kSlotCount = 10;
inline auto CountStr = Item<QString>("ag_fast_messages_count", u"1"_q);
[[nodiscard]] inline int Count() {
const auto v = CountStr.value().toInt();
return (v >= 1 && v <= kSlotCount) ? v : 1;
}
inline void SetCount(int n) {
CountStr.setValue(QString::number(n));
}
inline auto Slot1 = Item<QString>("ag_fast_message_text_1", QString());
inline auto Slot2 = Item<QString>("ag_fast_message_text_2", QString());
inline auto Slot3 = Item<QString>("ag_fast_message_text_3", QString());
@ -107,6 +117,15 @@ inline auto Slot10 = Item<QString>("ag_fast_message_text_10", QString());
Unexpected("FastMessages::Slot index out of range");
}
inline void RemoveSlot(int index) {
const auto count = Count();
for (auto i = index; i < count - 1; ++i) {
Slot(i).setValue(Slot(i + 1).value());
}
Slot(count - 1).setValue(QString());
SetCount(count - 1);
}
} // namespace FastMessages
} // namespace Arcanegram::Config

View file

@ -1,5 +1,6 @@
#include "ag_hooks.h"
#include "arcanegram/ag_lang.h"
#include "arcanegram/features/ag_forwarded_header.h"
#include "arcanegram/features/ag_show_seconds.h"
#include "arcanegram/features/ag_hidden_users.h"
@ -11,6 +12,7 @@
namespace Arcanegram::Hooks {
void init() {
Arcanegram::Lang::Init();
Arcanegram::ForwardedHeader::Init();
Arcanegram::Time::Init();
Arcanegram::HiddenUsers::Init();

View file

@ -0,0 +1,76 @@
#include "arcanegram/ag_lang.h"
#include "lang/lang_instance.h"
namespace Arcanegram::Lang {
namespace {
rpl::lifetime &Lifetime() {
static rpl::lifetime value;
return value;
}
constexpr auto kRuStrings = R"strings(
"ag_settings_title" = "Arcanegram";
"ag_forwarded_show_date_setting" = "Показывать дату в пересланных сообщениях";
"ag_forwarded_show_date_info" = "Добавлять исходную дату отправки к строке «Переслано от».";
"ag_forwarded_date_label" = "Дата";
"ag_show_seconds_setting" = "Показывать секунды в метках времени";
"ag_show_seconds_info" = "Отображать ЧЧ:ММ:СС вместо ЧЧ:ММ в метках времени сообщений и подсказках.";
"ag_chat_wallpaper_disabled_setting" = "Игнорировать фон чата";
"ag_chat_wallpaper_disabled_info" = "Всегда использовать фон по умолчанию; игнорировать фон, установленный чатами и каналами.";
"ag_hidden_users_title" = "Скрытые пользователи";
"ag_hidden_users_info" = "Сообщения от этих пользователей скрыты во всех чатах.";
"ag_hidden_users_empty" = "Нет скрытых пользователей. Нажмите правой кнопкой на аватар, чтобы скрыть сообщения.";
"ag_hidden_users_menu_hide" = "Скрыть сообщения от этого пользователя";
"ag_hidden_users_menu_unhide" = "Показать сообщения";
"ag_hidden_user_message" = "Сообщение от скрытого пользователя";
"ag_hidden_user_name" = "Скрытый пользователь";
"ag_hidden_users_remove" = "Удалить";
"ag_peer_id_copy_botapi" = "Копировать ID (Bot API)";
"ag_peer_id_copy_mtproto" = "Копировать ID (MTProto)";
"ag_fast_messages_title" = "Быстрые сообщения";
"ag_fast_messages_info" = "Введите сообщение для каждого слота. Привяжите клавиши в Настройках → Настройки чата → Горячие клавиши.";
"ag_fast_messages_slot" = "Слот {index}";
"ag_fast_messages_placeholder" = "Текст сообщения";
"ag_fast_messages_add" = "Добавить слот";
"ag_fast_messages_remove" = "Удалить слот";
"lng_shortcuts_fast_message" = "Быстрое сообщение {index}";
"ag_settings_appearance" = "Внешний вид чата";
"ag_settings_sync_title" = "Облачная синхронизация";
"ag_sync_enabled_setting" = "Включить облачную синхронизацию";
"ag_sync_enabled_info" = "Синхронизировать настройки Arcanegram между устройствами через arcanesync.";
"ag_sync_endpoint_placeholder" = "URL сервера";
"ag_sync_status_disabled" = "Отключено";
"ag_sync_status_ready" = "Готово";
"ag_sync_status_synced" = "Синхронизировано";
"ag_sync_status_syncing" = "Синхронизация…";
"ag_sync_status_offline" = "Не в сети";
"ag_sync_status_offline_pending" = "Не в сети · {n} ожидают";
"ag_sync_status_conflict" = "Разрешение конфликта…";
"ag_streamer_title" = "Режим стримера";
"ag_streamer_subtitle" = "Скрывать имена, аватары и идентификаторы при демонстрации экрана.";
"ag_streamer_enabled" = "Анонимизировать других пользователей";
"ag_streamer_enabled_about" = "Заменяет имя каждого пользователя псевдонимом, убирает аватар, цвет и значки, скрывает телефон, имя пользователя, ID, описание, день рождения и время последнего визита.";
"ag_streamer_anonymize_bots" = "Анонимизировать ботов";
"ag_streamer_anonymize_bots_about" = "Боты открыты по умолчанию, так как обычно это намеренно. Включите, чтобы также давать им псевдонимы.";
"ag_streamer_preview" = "Пример: {name}";
)strings";
void apply(const QString &langId) {
if (langId.startsWith(u"ru"_q)) {
::Lang::GetInstance().ag_loadContent(QByteArray(kRuStrings));
}
}
} // namespace
void Init() {
auto &instance = ::Lang::GetInstance();
apply(instance.id());
std::move(instance.idChanges()) | rpl::on_next([](const QString &id) {
apply(id);
}, Lifetime());
}
} // namespace Arcanegram::Lang

View file

@ -0,0 +1,7 @@
#pragma once
namespace Arcanegram::Lang {
void Init();
} // namespace Arcanegram::Lang

View file

@ -3,17 +3,20 @@
#include "apiwrap.h"
#include "arcanegram/ag_config.h"
#include "arcanegram/ui/ag_settings_main.h"
#include "base/invoke_queued.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_builder.h"
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
#include "styles/style_arcanegram.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
@ -24,29 +27,42 @@ namespace {
using namespace ::Settings;
using namespace ::Settings::Builder;
void AddSlotField(
not_null<Ui::VerticalLayout*> container,
int index) {
auto &item = Config::FastMessages::Slot(index);
Ui::AddSubsectionTitle(
container,
tr::ag_fast_messages_slot(
lt_index,
rpl::single(QString::number(index + 1))));
const auto field = container->add(
object_ptr<Ui::InputField>(
container,
st::agFastMessageField,
tr::ag_fast_messages_placeholder(),
item.value()),
st::settingsBioMargins);
field->changes(
) | rpl::on_next([field, &item](auto) {
const auto v = field->getLastText();
if (v != item.value()) {
item.setValue(v);
}
}, field->lifetime());
void FillSlots(not_null<Ui::VerticalLayout*> wrap, not_null<Ui::VerticalLayout*> outer) {
const auto count = Config::FastMessages::Count();
for (auto i = 0; i < count; ++i) {
auto &item = Config::FastMessages::Slot(i);
Ui::AddSubsectionTitle(
wrap,
tr::ag_fast_messages_slot(
lt_index,
rpl::single(QString::number(i + 1))));
const auto field = wrap->add(
object_ptr<Ui::InputField>(
wrap,
st::agFastMessageField,
tr::ag_fast_messages_placeholder(),
item.value()),
st::settingsBioMargins);
field->changes(
) | rpl::on_next([field, &item](auto) {
const auto v = field->getLastText();
if (v != item.value()) {
item.setValue(v);
}
}, field->lifetime());
const auto idx = i;
::Settings::AddButtonWithIcon(
wrap,
tr::ag_fast_messages_remove(),
st::settingsButton,
{ &st::menuIconDelete }
)->setClickedCallback([outer, idx] {
Config::FastMessages::RemoveSlot(idx);
outer->resizeToWidth(outer->width());
});
Ui::AddSkip(wrap);
Ui::AddDivider(wrap);
}
}
class Page : public Section<Page> {
@ -62,12 +78,38 @@ private:
};
void BuildPage(SectionBuilder &builder) {
const auto container = builder.container();
for (auto i = 0; i != Config::FastMessages::kSlotCount; ++i) {
AddSlotField(container, i);
}
Ui::AddSkip(container);
Ui::AddDividerText(container, tr::ag_fast_messages_info());
const auto outer = builder.container();
const auto slotWrap = outer->add(object_ptr<Ui::VerticalLayout>(outer));
FillSlots(slotWrap, outer);
Config::FastMessages::CountStr.changes(
) | rpl::on_next([outer, slotWrap](auto) {
InvokeQueued(slotWrap, [outer, slotWrap] {
while (slotWrap->count()) {
delete slotWrap->widgetAt(0);
}
FillSlots(slotWrap, outer);
slotWrap->resizeToWidth(slotWrap->width());
});
}, slotWrap->lifetime());
Ui::AddSkip(outer);
::Settings::AddButtonWithIcon(
outer,
tr::ag_fast_messages_add(),
st::settingsButton,
{ &st::menuIconAdd }
)->setClickedCallback([outer] {
const auto count = Config::FastMessages::Count();
if (count >= Config::FastMessages::kSlotCount) {
return;
}
Config::FastMessages::SetCount(count + 1);
});
Ui::AddSkip(outer);
Ui::AddDividerText(outer, tr::ag_fast_messages_info());
}
const auto kPageMeta = BuildHelper({
@ -103,7 +145,7 @@ bool Send(
not_null<PeerData*> peer,
FullReplyTo replyTo,
int slot) {
if (slot < 0 || slot >= Config::FastMessages::kSlotCount) {
if (slot < 0 || slot >= Config::FastMessages::Count()) {
return false;
}
const auto text = Config::FastMessages::Slot(slot).value().trimmed();

View file

@ -18,7 +18,6 @@
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "ui/vertical_list.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
@ -173,18 +172,11 @@ void BuildPage(::Settings::Builder::SectionBuilder &builder) {
tr::ag_streamer_anonymize_bots_about(),
Config::StreamerMode::AnonymizeBots);
Ui::AddSkip(container);
Ui::AddDivider(container);
Ui::AddSkip(container);
container->add(
object_ptr<Ui::FlatLabel>(
container,
tr::ag_streamer_preview(
lt_name,
rpl::single(GeneratedName(PeerId(1234567890ULL)))),
st::boxDividerLabel),
st::defaultBoxDividerLabelPadding);
Ui::AddDividerText(
container,
tr::ag_streamer_preview(
lt_name,
rpl::single(GeneratedName(PeerId(1234567890ULL)))));
Ui::AddDividerText(container, tr::ag_streamer_subtitle());
}