Compare commits
437 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 214f48922e | |||
| 84e7ff2637 | |||
| 40e1f307a7 | |||
| 27f4f73690 | |||
| 7321b654bf | |||
| 754d467440 | |||
| 598f08d6c7 | |||
| 224fdc1864 | |||
| e646b4dc9a | |||
| 9077db2e97 | |||
| 7e14277ead | |||
| d351a7d697 | |||
| 70ed43b811 | |||
| 913083ebc6 | |||
| 588a95a7ae | |||
| c0a0ad4ec5 | |||
| 5eafe96525 | |||
| b41ac0fc2a | |||
| cfe93530b8 | |||
| 4492e72ffa | |||
| 1a0fd35f36 | |||
| db475ef0b4 | |||
| b3dddc1dfe | |||
| b64c610abb | |||
| c5add2fca9 | |||
| 9ea78f7d28 | |||
| 54b0d965ae | |||
| 7b4bd5696b | |||
| 9064f3ba4b | |||
| e00e562b5f | |||
| 73f38c896f | |||
| 5767cbd0e3 | |||
| ba31756bf9 | |||
| a88f48cd93 | |||
| f2e0e481de | |||
| 566f279137 | |||
| 394ef13955 | |||
| 6812e17d07 | |||
| fdf826b686 | |||
| e57742e7de | |||
| 73b63aa414 | |||
| 44aa2aec5d | |||
| 8d9d7c4cea | |||
| 9557f0c844 | |||
| a32a9aa3fc | |||
| 86fa98dfbb | |||
| 6a69447d90 | |||
| 2d20e7a9e2 | |||
| ac7b2e0da0 | |||
| 388325a496 | |||
| af728e82fc | |||
| 3cb33f0825 | |||
| 1038baf467 | |||
| 828ecabc78 | |||
| 88703ba1eb | |||
| b0ecb2c535 | |||
| c70482dbc4 | |||
| 51f1999412 | |||
| dd4fbc256c | |||
| cc2265583f | |||
| 1e7a4db57f | |||
| 9a9e30c88b | |||
| 3d98ebff42 | |||
| a6f4b1ae8e | |||
| 3e6ea8109c | |||
| ec407d57a5 | |||
| 838ad66166 | |||
| c6bf905253 | |||
| 8310230582 | |||
| 1f21af0bdb | |||
| 127e4a5086 | |||
| 6a30967f23 | |||
| df2b020b42 | |||
| a74c5a89a6 | |||
| 3cb0be5be5 | |||
| d56f3cfecf | |||
| 9716a901d1 | |||
| 649d242e9a | |||
| 8ac3c2157f | |||
| 7ca4ca21fa | |||
| e565acba91 | |||
| 260678634e | |||
| 45461836e1 | |||
| 1fc230624d | |||
| ede771e51b | |||
| 0282786b4c | |||
| 42dd08ace5 | |||
| cf1d274b0d | |||
| d361d5f3b2 | |||
| 3e63b40564 | |||
| c5139ed06a | |||
| 3dc93526ff | |||
| 3edf8e10e2 | |||
| 00215622cc | |||
| 479b7c3140 | |||
| a3ca8ddcfc | |||
| 9ace04d2c9 | |||
| 8b11d2d5e7 | |||
| 05fb0f81f9 | |||
| da49efa1ed | |||
| 39fb0a5b66 | |||
| 962d4d29ee | |||
| 779e9b658b | |||
| f9ee4dcb51 | |||
| 1acdbb69ae | |||
| c022a1c838 | |||
| 52afd3d5a8 | |||
| 79b1c0edee | |||
| 4803bd4b3f | |||
| 215a262076 | |||
| 7a35577d3a | |||
| efbd3ca8fa | |||
| 18904412cd | |||
| 0d0d0ab994 | |||
| 71deef61f5 | |||
| 46c8a55f56 | |||
| 37f837dcb7 | |||
| a5be0a685a | |||
| 4cdd1fec95 | |||
| 761617c1ce | |||
| e3bc4dab85 | |||
| 335095a332 | |||
| 15fcb73e19 | |||
| a8b0f2934b | |||
| 7d67b3d00a | |||
| b60c7e97ab | |||
| 440ebfcbf6 | |||
| 8d93eb919b | |||
| d3df2dc1e5 | |||
| 677314dacb | |||
| 20f53c89ad | |||
| 1cb92ef69a | |||
| b518f7c4c4 | |||
| 9fdc099c3a | |||
| 1db8ada2aa | |||
| b753448052 | |||
| e29c6d2f23 | |||
| 1ee53ee0c1 | |||
| 57438867b6 | |||
| 86c04424f6 | |||
| 632639d581 | |||
| 99c8edb3eb | |||
| 4582e61cfc | |||
| a970fe93c1 | |||
| 91f5c72cf0 | |||
| d2f3d04ad4 | |||
| 07b93e1e02 | |||
| ab5e7b1588 | |||
| 0f4aadddfc | |||
| a3c79feeba | |||
| e6b76fefa1 | |||
| a37f512949 | |||
| 3f34c0ce37 | |||
| 7cc9a0b9aa | |||
| fbaa79f168 | |||
| 87808ef317 | |||
| 80c7e4e74c | |||
| e778aab8b7 | |||
| 976db74ece | |||
| b073810bab | |||
| 2b0aa8e418 | |||
| 1fa2e76c9b | |||
| cb166e2591 | |||
| 187c2dda20 | |||
| b962d2b550 | |||
| d69bcfa138 | |||
| 157b25ea43 | |||
| 2b01b63ee1 | |||
| 3d91d2c80a | |||
| 625f9ef816 | |||
| 0960a3b21d | |||
| aec220fd2c | |||
| 666dacf73b | |||
| 67ce27afaa | |||
| 3f6d184435 | |||
| a2fad84dae | |||
| 4a84f9fa00 | |||
| 7abc921d20 | |||
| a307e7a798 | |||
| 3a9eb8463d | |||
| 4befb125e3 | |||
| ca00a19736 | |||
| 296969bcd1 | |||
| 3218c30983 | |||
| 3658b32db5 | |||
| 592d46c8f2 | |||
| 8b86f12c23 | |||
| 2e2d8d2af3 | |||
| 3eec43cacd | |||
| ba7cd25f21 | |||
| 969152e949 | |||
| b78443cf2d | |||
| 4288ba2449 | |||
| 22a3093815 | |||
| 98ba2c7ce4 | |||
| 36962b8c62 | |||
| d0a8bd1f03 | |||
| c9e0f50f0f | |||
| 1dc310586d | |||
| 6f71d21bb7 | |||
| 7caf421074 | |||
| c93f047056 | |||
| 392f2e633c | |||
| ef98d4ece7 | |||
| 2383bf2c71 | |||
| 2c8c92c2a4 | |||
| bad2dc30c3 | |||
| fe7f4233b9 | |||
| 68454a9841 | |||
| d3bcf63cf7 | |||
| a5f1209f28 | |||
| 66b7a48352 | |||
| 5bd66f9a76 | |||
| 6da6817037 | |||
| 11b3880f8e | |||
| a3d87ff26a | |||
| 409eedf58d | |||
| 9a44ca2769 | |||
| 155305f0f7 | |||
| 6abce8d976 | |||
| f16d1f034f | |||
| dab107cf90 | |||
| d1d1aa3d21 | |||
| d0536cc31f | |||
| c47f5e9995 | |||
| 0916836ff9 | |||
| 5dd9ff1062 | |||
| d1e3b9f15d | |||
| ca3c179b75 | |||
| c2d5924508 | |||
| 016d0395c3 | |||
| 925c9239bd | |||
| fdcbe3cf3a | |||
| f6b9cc5ce1 | |||
| 8c915e6dc3 | |||
| 1db426da2e | |||
| d9be363962 | |||
| 88daa37e34 | |||
| 931fa01337 | |||
| 8e1595eb29 | |||
| 29e97232d8 | |||
| f05191e668 | |||
| 1ba189e59d | |||
| c40ca70aa6 | |||
| 521f991167 | |||
| edf1417bbb | |||
| 686e9643ad | |||
| 975460d268 | |||
| 6b0c606d25 | |||
| 93ff0bdcff | |||
| bf9d90ca4e | |||
| d6e5e1e8f7 | |||
| d3ae2ef9ea | |||
| 40b0854704 | |||
| 9aec6b6496 | |||
| d01f977960 | |||
| 1444009ee2 | |||
| 14ee9bee26 | |||
| 93587ddc3c | |||
| 0c4d03477e | |||
| a35092f012 | |||
| 49ee7ee52b | |||
| 52c77a1970 | |||
| e4269ae7fb | |||
| bd6011c524 | |||
| 46cb3ec103 | |||
| 0241129948 | |||
| e44aca06cb | |||
| 19d386f977 | |||
| a67fdda913 | |||
| 5286c7b1c3 | |||
| d9d96d0a6f | |||
| 13353bb615 | |||
| d78348fd16 | |||
| 1e8e660133 | |||
| 5196982c98 | |||
| 99f857fbf6 | |||
| 0982aa166a | |||
| 3ae9f86097 | |||
| 9efd9b0d68 | |||
| 267a73f355 | |||
| 437d8ea890 | |||
| 7ed92ec402 | |||
| 678d9ffbf9 | |||
| bc4b427ed1 | |||
| 36141a9df9 | |||
| d68ba75457 | |||
| 00ad32c5f4 | |||
| 064bab60ff | |||
| 5b146217c0 | |||
| 202c81b2e5 | |||
| 4520480604 | |||
| aea87bb5cb | |||
| 19492f7e7b | |||
| 51030e3c45 | |||
| e6b8b4be18 | |||
| 6ac13b7f80 | |||
| 7e5e6003a9 | |||
| f445440995 | |||
| d81547f091 | |||
| 2b185d491b | |||
| ca47440950 | |||
| 5e32602f4a | |||
| 1f4516028c | |||
| 7f29d269a3 | |||
| 62c3374911 | |||
| 2116e04af5 | |||
| a97d5e80c7 | |||
| b5098038d0 | |||
| 6fce718252 | |||
| 40cf96202d | |||
| c18e59218e | |||
| 216865a20d | |||
| 6c5036ee8d | |||
| dc2f59ca24 | |||
| 6f6457137e | |||
| bc5cb6e2a2 | |||
| 16825fff41 | |||
| 5024f1db8c | |||
| a60385fc3d | |||
| b20e2c37c1 | |||
| acd40cbeb6 | |||
| 6a45a862dd | |||
| 5bd45e9a20 | |||
| 52e42f23ab | |||
| 96b7755cde | |||
| c589ee1ca5 | |||
| 18c9ee093b | |||
| b82fa3112c | |||
| 8d0f66d562 | |||
| 20a5e0ba73 | |||
| 5a2667c71e | |||
| 1f4a8d7eb6 | |||
| 4430bd0328 | |||
| ab2e7f4c03 | |||
| 54e5c06b4d | |||
| add5a6a0be | |||
| 89c2ba4293 | |||
| dd100fb709 | |||
| e8dd2b9e7b | |||
| 71e3cd227c | |||
| 1648c31a22 | |||
| f8c820f319 | |||
| 300f35e78f | |||
| 2aa5849997 | |||
| fb1b845211 | |||
| 3d2af9db8e | |||
| 6129e5a1cf | |||
| 7f70ee1227 | |||
| c9f7da6e82 | |||
| f956c0f227 | |||
| df935e0477 | |||
| 841f1afe1e | |||
| fb8b88557e | |||
| 2b502b22b9 | |||
| 5ac80d2655 | |||
| 8aa7499e63 | |||
| 8cd5e51982 | |||
| e5c2133446 | |||
| 3dccdf2f05 | |||
| 8a708c6655 | |||
| 9e1d9eee4b | |||
| f30aabc365 | |||
| a5546d016f | |||
| f79d70d112 | |||
| ec28f258fb | |||
| 08f3a6fb40 | |||
| 8823d5256f | |||
| 60e7aa90d2 | |||
| 71357a9546 | |||
| a5b06e9c56 | |||
| 0f94419f6d | |||
| 94e7aabea5 | |||
| f6c816cafe | |||
| 4cf160e8dc | |||
| 9252be5e8c | |||
| ed342eea64 | |||
| e6405bc455 | |||
| fed09461ce | |||
| 6e1ddef4fe | |||
| 76be4a3eb9 | |||
| 997a9e2fe3 | |||
| 03790f3da0 | |||
| e46a703c7d | |||
| ae17acdfd4 | |||
| 2422c9ce9e | |||
| 7f75d7082a | |||
| 4749ab175e | |||
| e651699e1d | |||
| 3f4157bab2 | |||
| a98f559066 | |||
| ffd54c452c | |||
| 61424eeab9 | |||
| c131d6637d | |||
| 24b0b33f1d | |||
| 675198d361 | |||
| ed667a42ad | |||
| 920484d540 | |||
| 07c5e6542b | |||
| 6a43f2e508 | |||
| ad3e447f08 | |||
| 1840da1d68 | |||
| c82b86cea3 | |||
| 0fb383c466 | |||
| 69fc2f48bf | |||
| 7c604fc86a | |||
| ec7fbb8952 | |||
| 679350e23d | |||
| f0209c9d6e | |||
| 5020aec6ec | |||
| 13737577e7 | |||
| 4ee7d46d78 | |||
| d143e32022 | |||
| 0a0dab74a1 | |||
| c6ea91e671 | |||
| 8011adb219 | |||
| 6ba5d5f16b | |||
| d5774830d8 | |||
| eb268102fc | |||
| b364c4f23a | |||
| 91b9266a91 | |||
| 1e6236a987 | |||
| 10ebd7e6ef | |||
| 6b83c52c7c | |||
| 46304c7a2d | |||
| c3fda41224 | |||
| a4017e930e | |||
| 95bdb925d5 | |||
| a26bae70c7 | |||
| a38dcb6ee5 | |||
| eb27c12117 | |||
| c03fcf9a23 | |||
| 71f83b5993 | |||
| fd2d12d6b1 | |||
| 50f9f36c3d | |||
| f650c679e0 | |||
| 8349cb0dd4 |
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-2022
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -94,6 +94,14 @@ jobs:
|
||||
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
|
||||
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
|
||||
|
||||
- name: ThirdParty cache.
|
||||
id: cache-third-party
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.TBUILD }}\ThirdParty
|
||||
key: ${{ runner.OS }}-${{ matrix.arch }}-third-party-${{ env.CACHE_KEY }}
|
||||
restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-third-party-
|
||||
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v4
|
||||
|
||||
@@ -51,3 +51,6 @@ stage
|
||||
*.*~
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
|
||||
# Project specific
|
||||
Telegram/SourceFiles/_other/packer_private.h
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# AyuGram
|
||||
|
||||
 
|
||||
|
||||
[ [English](README.md) | Русский ]
|
||||
|
||||
## Функции и Фишки
|
||||
|
||||
- Полный режим призрака (настраиваемый)
|
||||
- История удалений и изменений сообщений
|
||||
- Кастомизация шрифта
|
||||
- Режим Стримера
|
||||
- Локальный телеграм премиум
|
||||
- Превью медиа и быстрая реакция при сильном нажатии на тачпад (macOS)
|
||||
- Улучшенный вид
|
||||
|
||||
И многое другое. Посмотрите нашу [Документацию](https://docs.ayugram.one/desktop/) для более подробной информации.
|
||||
|
||||
## Скриншоты настроек
|
||||
|
||||
<img src='.github/demos/demo1.png' width='268'>
|
||||
<img src='.github/demos/demo3.png' width='268'>
|
||||
<img src='.github/demos/demo2.png' width='268'>
|
||||
<img src='.github/demos/demo4.png' width='268'>
|
||||
|
||||
## Установка
|
||||
|
||||
### Windows
|
||||
|
||||
#### Официальный вариант
|
||||
|
||||
Вы можете скачать готовый бинарный файл со [вкладки Releases](https://github.com/AyuGram/AyuGramDesktop/releases) или из
|
||||
[Телеграм чата](https://t.me/ayugramchat/12788).
|
||||
|
||||
#### Winget
|
||||
|
||||
```bash
|
||||
winget install RadolynLabs.AyuGramDesktop
|
||||
```
|
||||
|
||||
#### Scoop
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install ayugram
|
||||
```
|
||||
|
||||
#### Сборка вручную
|
||||
|
||||
Следуйте [официальному руководству](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-win-x64.md), если
|
||||
вы хотите собрать AyuGram сами.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Вы можете установить `ayugram-desktop` или `ayugram-desktop-git`
|
||||
из [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
|
||||
|
||||
### NixOS
|
||||
|
||||
Попробуйте [этот репозиторий](https://github.com/kaeeraa/ayugram-desktop).
|
||||
|
||||
### Любой другой Линукс дистрибутив
|
||||
|
||||
Следуйте [официальному руководству](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-linux.md).
|
||||
|
||||
### Примечания для Windows
|
||||
|
||||
Убедитесь что у вас присутствуют эти зависимости:
|
||||
|
||||
- C++ MFC latest (x86 & x64)
|
||||
- C++ ATL latest (x86 & x64)
|
||||
- последний Windows 11 SDK
|
||||
|
||||
## Пожертвования
|
||||
|
||||
Вам нравится использовать **AyuGram**? Оставьте нам чаевые!
|
||||
|
||||
[Здесь доступные варианты.](https://docs.ayugram.one/donate/)
|
||||
|
||||
## Использованные материалы
|
||||
|
||||
### Телеграм клиенты
|
||||
|
||||
- [Telegram Desktop](https://github.com/telegramdesktop/tdesktop)
|
||||
- [Kotatogram](https://github.com/kotatogram/kotatogram-desktop)
|
||||
- [64Gram](https://github.com/TDesktop-x64/tdesktop)
|
||||
- [Forkgram](https://github.com/forkgram/tdesktop)
|
||||
|
||||
### Использованные библиотеки
|
||||
|
||||
- [JSON for Modern C++](https://github.com/nlohmann/json)
|
||||
- [SQLite](https://github.com/sqlite/sqlite)
|
||||
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
|
||||
|
||||
### Иконки
|
||||
|
||||
- [Solar Icon Set](https://www.figma.com/community/file/1166831539721848736)
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
 
|
||||
|
||||
[ English | [Русский](README-RU.md) ]
|
||||
|
||||
## Features
|
||||
|
||||
- Full ghost mode (flexible)
|
||||
@@ -10,6 +12,7 @@
|
||||
- Font customization
|
||||
- Streamer mode
|
||||
- Local Telegram Premium
|
||||
- Media preview & quick reaction on force click (macOS)
|
||||
- Enhanced appearance
|
||||
|
||||
And many more. Check out our [Documentation](https://docs.ayugram.one/desktop/).
|
||||
|
||||
@@ -37,6 +37,10 @@ include(cmake/td_scheme.cmake)
|
||||
include(cmake/td_ui.cmake)
|
||||
include(cmake/generate_appdata_changelog.cmake)
|
||||
|
||||
if (DESKTOP_APP_TEST_APPS)
|
||||
include(cmake/tests.cmake)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}
|
||||
@@ -132,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
|
||||
@@ -390,12 +394,16 @@ PRIVATE
|
||||
boxes/self_destruction_box.h
|
||||
boxes/send_credits_box.cpp
|
||||
boxes/send_credits_box.h
|
||||
boxes/send_gif_with_caption_box.cpp
|
||||
boxes/send_gif_with_caption_box.h
|
||||
boxes/send_files_box.cpp
|
||||
boxes/send_files_box.h
|
||||
boxes/sessions_box.cpp
|
||||
boxes/sessions_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
boxes/star_gift_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
@@ -643,6 +651,8 @@ PRIVATE
|
||||
data/data_lastseen_status.h
|
||||
data/data_location.cpp
|
||||
data/data_location.h
|
||||
data/data_media_preload.cpp
|
||||
data/data_media_preload.h
|
||||
data/data_media_rotation.cpp
|
||||
data/data_media_rotation.h
|
||||
data/data_media_types.cpp
|
||||
@@ -678,6 +688,7 @@ PRIVATE
|
||||
data/data_replies_list.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_report.h
|
||||
data/data_saved_messages.cpp
|
||||
data/data_saved_messages.h
|
||||
data/data_saved_sublist.cpp
|
||||
@@ -1021,6 +1032,10 @@ PRIVATE
|
||||
info/media/info_media_widget.h
|
||||
info/members/info_members_widget.cpp
|
||||
info/members/info_members_widget.h
|
||||
info/peer_gifts/info_peer_gifts_common.cpp
|
||||
info/peer_gifts/info_peer_gifts_common.h
|
||||
info/peer_gifts/info_peer_gifts_widget.cpp
|
||||
info/peer_gifts/info_peer_gifts_widget.h
|
||||
info/polls/info_polls_results_inner_widget.cpp
|
||||
info/polls/info_polls_results_inner_widget.h
|
||||
info/polls/info_polls_results_widget.cpp
|
||||
@@ -1556,6 +1571,8 @@ PRIVATE
|
||||
support/support_templates.h
|
||||
ui/boxes/edit_invite_link_session.cpp
|
||||
ui/boxes/edit_invite_link_session.h
|
||||
ui/boxes/peer_qr_box.cpp
|
||||
ui/boxes/peer_qr_box.h
|
||||
ui/chat/attach/attach_item_single_file_preview.cpp
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
@@ -1564,6 +1581,8 @@ PRIVATE
|
||||
ui/chat/choose_send_as.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/controls/emoji_button_factory.cpp
|
||||
ui/controls/emoji_button_factory.h
|
||||
ui/controls/location_picker.cpp
|
||||
ui/controls/location_picker.h
|
||||
ui/controls/silent_toggle.cpp
|
||||
@@ -1589,6 +1608,8 @@ PRIVATE
|
||||
ui/image/image_location_factory.h
|
||||
ui/text/format_song_document_name.cpp
|
||||
ui/text/format_song_document_name.h
|
||||
ui/widgets/expandable_peer_list.cpp
|
||||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/countryinput.cpp
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 832 KiB After Width: | Height: | Size: 935 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 234 KiB |
@@ -582,3 +582,55 @@ div.toast_shown {
|
||||
.bot_button_column_separator {
|
||||
width: 2px
|
||||
}
|
||||
|
||||
.reactions {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.reactions .reaction {
|
||||
display: inline-flex;
|
||||
height: 20px;
|
||||
border-radius: 15px;
|
||||
background-color: #e8f5fc;
|
||||
color: #168acd;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.reactions .reaction.active {
|
||||
background-color: #40a6e2;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reactions .reaction.paid {
|
||||
background-color: #fdf6e1;
|
||||
color: #c58523;
|
||||
}
|
||||
|
||||
.reactions .reaction.active.paid {
|
||||
background-color: #ecae0a;
|
||||
color: #fdf6e1;
|
||||
}
|
||||
|
||||
.reactions .reaction .emoji {
|
||||
line-height: 20px;
|
||||
margin: 0 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic:not(:first-child) {
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic .initials {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .count {
|
||||
margin-right: 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 444 B |
|
After Width: | Height: | Size: 760 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 514 B |
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 891 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 499 B |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 426 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 240 B |
|
After Width: | Height: | Size: 324 B |
|
After Width: | Height: | Size: 422 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="1000px" height="1000px" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M226.328419,494.722069 C372.088573,431.216685 469.284839,389.350049 517.917216,369.122161 C656.772535,311.36743 685.625481,301.334815 704.431427,301.003532 C708.567621,300.93067 717.815839,301.955743 723.806446,306.816707 C728.864797,310.92121 730.256552,316.46581 730.922551,320.357329 C731.588551,324.248848 732.417879,333.113828 731.758626,340.040666 C724.234007,419.102486 691.675104,610.964674 675.110982,699.515267 C668.10208,736.984342 654.301336,749.547532 640.940618,750.777006 C611.904684,753.448938 589.856115,731.588035 561.733393,713.153237 C517.726886,684.306416 492.866009,666.349181 450.150074,638.200013 C400.78442,605.66878 432.786119,587.789048 460.919462,558.568563 C468.282091,550.921423 596.21508,434.556479 598.691227,424.000355 C599.00091,422.680135 599.288312,417.758981 596.36474,415.160431 C593.441168,412.561881 589.126229,413.450484 586.012448,414.157198 C581.598758,415.158943 511.297793,461.625274 375.109553,553.556189 C355.154858,567.258623 337.080515,573.934908 320.886524,573.585046 C303.033948,573.199351 268.692754,563.490928 243.163606,555.192408 C211.851067,545.013936 186.964484,539.632504 189.131547,522.346309 C190.260287,513.342589 202.659244,504.134509 226.328419,494.722069 Z" id="Path-3" fill="#FFFFFF"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 641 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -12,6 +12,7 @@ body {
|
||||
margin: 0;
|
||||
background-color: var(--td-window-bg);
|
||||
color: var(--td-window-fg);
|
||||
zoom: var(--td-zoom-percentage);
|
||||
}
|
||||
|
||||
html.custom_scroll ::-webkit-scrollbar {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
|
||||
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
|
||||
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
|
||||
<file alias="plane_white.svg">../../icons/plane_white.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/icons">
|
||||
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.4.1.0" />
|
||||
Version="5.6.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,1,0
|
||||
PRODUCTVERSION 5,4,1,0
|
||||
FILEVERSION 5,6,3,0
|
||||
PRODUCTVERSION 5,6,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.4.1.0"
|
||||
VALUE "FileVersion", "5.6.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.4.1.0"
|
||||
VALUE "ProductVersion", "5.6.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,1,0
|
||||
PRODUCTVERSION 5,4,1,0
|
||||
FILEVERSION 5,6,3,0
|
||||
PRODUCTVERSION 5,6,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.4.1.0"
|
||||
VALUE "FileVersion", "5.6.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.4.1.0"
|
||||
VALUE "ProductVersion", "5.6.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -13,9 +13,9 @@ bool OnlyAlphaKey = false;
|
||||
|
||||
const char *PublicKey = "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\
|
||||
BZpkIfKaRcl6XzNJiN28cVwO1Ui5JSa814UAiDHzWUqCaXUiUEQ6NmNTneiGx2sQ\n\
|
||||
+9PKKlb8mmr3BB9A45ZNwLT6G9AK3+qkZLHojeSA+m84/a6GP4svAgMBAAE=\n\
|
||||
MIGJAoGBAOIENxe1sfT2t7b+HUMpnT6RnN/sCqY0JjK7/1A/59daDc6i/K4023jw\n\
|
||||
Us+187Pa2VSaPh3kDywp9PfLDFgFiPTD9BsBvpaSK9p3zyG8k6an3+GbUTlUgmFv\n\
|
||||
eI0pg7vmceIl0Lcy9nndfEx27UQId3Y3dQTMuwwFYGtjJAMHNNq5AgMBAAE=\n\
|
||||
-----END RSA PUBLIC KEY-----\
|
||||
";
|
||||
|
||||
@@ -29,8 +29,7 @@ w/CVnbwQOw0g5GBwwFV3r0uTTvy44xx8XXxk+Qknu4eBCsmrAFNnAgMBAAE=\n\
|
||||
|
||||
extern const char *PrivateKey;
|
||||
extern const char *PrivateBetaKey;
|
||||
#include "../../../../DesktopPrivate/packer_private.h" // RSA PRIVATE KEYS for update signing
|
||||
#include "../../../../DesktopPrivate/alpha_private.h" // private key for alpha version file generation
|
||||
#include "packer_private.h" // RSA PRIVATE KEYS for update signing
|
||||
|
||||
QString countAlphaVersionSignature(quint64 version);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
NSString *appName = @"Telegram.app";
|
||||
NSString *appName = @"AyuGram.app";
|
||||
NSString *appDir = nil;
|
||||
NSString *workDir = nil;
|
||||
|
||||
@@ -56,7 +56,7 @@ void RemoveQuarantineAttribute(NSString *path) {
|
||||
|
||||
void RemoveQuarantineFromBundle(NSString *path) {
|
||||
RemoveQuarantineAttribute(path);
|
||||
RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/MacOS/Telegram"]);
|
||||
RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/MacOS/AyuGram"]);
|
||||
RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/Helpers/crashpad_handler"]);
|
||||
RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/Frameworks/Updater"]);
|
||||
}
|
||||
@@ -163,9 +163,9 @@ int main(int argc, const char * argv[]) {
|
||||
|
||||
writeLog([@"Starting update files iteration, path: " stringByAppendingString: srcEnum]);
|
||||
|
||||
// Take the Updater (this currently running binary) from the place where it was placed by Telegram
|
||||
// Take the Updater (this currently running binary) from the place where it was placed by AyuGram
|
||||
// and copy it to the folder with the new version of the app (ready),
|
||||
// so it won't be deleted when we will clear the "Telegram.app/Contents" folder.
|
||||
// so it won't be deleted when we will clear the "AyuGram.app/Contents" folder.
|
||||
NSString *oldVersionUpdaterPath = [appDirFull stringByAppendingString: @"/Contents/Frameworks/Updater" ];
|
||||
NSString *newVersionUpdaterPath = [srcEnum stringByAppendingString:[[NSArray arrayWithObjects:@"/", appName, @"/Contents/Frameworks/Updater", nil] componentsJoinedByString:@""]];
|
||||
writeLog([[NSArray arrayWithObjects: @"Copying Updater from old path ", oldVersionUpdaterPath, @" to new path ", newVersionUpdaterPath, nil] componentsJoinedByString:@""]);
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/url_auth_box.h"
|
||||
#include "boxes/peers/choose_peer_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "chat_helpers/bot_command.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -38,6 +39,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
@@ -502,11 +506,19 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = {.text = button->text, .url = button->data },
|
||||
.button = { .text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = true },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case ButtonType::CopyText: {
|
||||
const auto text = QString::fromUtf8(button->data);
|
||||
if (!text.isEmpty()) {
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
controller->showToast(tr::lng_text_copied(tr::now));
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_filter_icons.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -231,12 +232,12 @@ void ImportInvite(
|
||||
api->request(MTPchatlists_JoinChatlistInvite(
|
||||
MTP_string(slug),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
} else {
|
||||
api->request(MTPchatlists_JoinChatlistUpdates(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +517,8 @@ void ShowImportError(
|
||||
} else {
|
||||
window->showToast((error == u"INVITE_SLUG_EXPIRED"_q)
|
||||
? tr::lng_group_invite_bad_link(tr::now)
|
||||
: error.startsWith(u"FLOOD_WAIT_"_q)
|
||||
? tr::lng_flood_error(tr::now)
|
||||
: error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,6 @@ void ConfirmSubscriptionBox(
|
||||
: 0;
|
||||
state->api->request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(formId),
|
||||
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
|
||||
).done([=](const MTPpayments_PaymentResult &result) {
|
||||
@@ -367,12 +366,15 @@ void CheckChatInvite(
|
||||
result.match([=](const MTPDchatInvite &data) {
|
||||
const auto isGroup = !data.is_broadcast();
|
||||
const auto hasPricing = !!data.vsubscription_pricing();
|
||||
if (hasPricing && !data.vsubscription_form_id()) {
|
||||
const auto canRefulfill = data.is_can_refulfill_subscription();
|
||||
if (hasPricing
|
||||
&& !canRefulfill
|
||||
&& !data.vsubscription_form_id()) {
|
||||
strong->uiShow()->showToast(
|
||||
tr::lng_confirm_phone_link_invalid(tr::now));
|
||||
return;
|
||||
}
|
||||
const auto box = hasPricing
|
||||
const auto box = (hasPricing && !canRefulfill)
|
||||
? strong->show(Box(
|
||||
ConfirmSubscriptionBox,
|
||||
session,
|
||||
|
||||
@@ -264,14 +264,24 @@ ChatParticipant::ChatParticipant(
|
||||
_rank = qs(data.vrank().value_or_empty());
|
||||
_rights = ChatAdminRightsInfo(data.vadmin_rights());
|
||||
_by = peerToUser(peerFromUser(data.vpromoted_by()));
|
||||
_date = data.vdate().v;
|
||||
}, [&](const MTPDchannelParticipantSelf &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
_by = peerToUser(peerFromUser(data.vinviter_id()));
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipantBanned &data) {
|
||||
_restrictions = ChatRestrictionsInfo(data.vbanned_rights());
|
||||
_by = peerToUser(peerFromUser(data.vkicked_by()));
|
||||
_date = data.vdate().v;
|
||||
|
||||
_type = (_restrictions.flags & ChatRestriction::ViewMessages)
|
||||
? Type::Banned
|
||||
@@ -348,6 +358,24 @@ ChatAdminRightsInfo ChatParticipant::rights() const {
|
||||
return _rights;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::subscriptionDate() const {
|
||||
return _subscriptionDate;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::promotedSince() const {
|
||||
return (_type == Type::Admin) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::restrictedSince() const {
|
||||
return (_type == Type::Restricted || _type == Type::Banned)
|
||||
? _date
|
||||
: TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::memberSince() const {
|
||||
return (_type == Type::Member) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
ChatParticipant::Type ChatParticipant::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ public:
|
||||
ChatRestrictionsInfo restrictions() const;
|
||||
ChatAdminRightsInfo rights() const;
|
||||
|
||||
TimeId subscriptionDate() const;
|
||||
TimeId promotedSince() const;
|
||||
TimeId restrictedSince() const;
|
||||
TimeId memberSince() const;
|
||||
|
||||
Type type() const;
|
||||
QString rank() const;
|
||||
|
||||
@@ -73,6 +78,8 @@ private:
|
||||
bool _canBeEdited = false;
|
||||
|
||||
QString _rank;
|
||||
TimeId _subscriptionDate = 0;
|
||||
TimeId _date = 0;
|
||||
|
||||
ChatRestrictionsInfo _restrictions;
|
||||
ChatAdminRightsInfo _rights;
|
||||
|
||||
@@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -69,16 +70,23 @@ constexpr auto kTransactionsLimit = 100;
|
||||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto incoming = (int64(tl.data().vstars().v) >= 0);
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.description = qs(tl.data().vdescription().value_or_empty()),
|
||||
.description = { qs(tl.data().vdescription().value_or_empty()) },
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -102,12 +110,16 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.convertStars = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.converted = stargift && incoming,
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
.gift = tl.data().is_gift(),
|
||||
.in = incoming,
|
||||
.gift = tl.data().is_gift() || stargift.has_value(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -215,6 +227,10 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
@@ -233,6 +249,8 @@ void CreditsStatus::request(
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
const auto balance = result.data().vbalance().v;
|
||||
_peer->session().credits().apply(_peer->id, balance);
|
||||
if (const auto onstack = done) {
|
||||
onstack(StatusFromTL(result, _peer));
|
||||
}
|
||||
@@ -294,10 +312,6 @@ void CreditsHistory::requestSubscriptions(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto username = session->appConfig().get<QString>(
|
||||
@@ -385,4 +399,58 @@ Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsGiveawayOption;
|
||||
|
||||
const auto optionsFromTL = [=](const auto &options) {
|
||||
return ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([=](const auto &option) {
|
||||
return Data::CreditsGiveawayOption{
|
||||
.winners = ranges::views::all(
|
||||
option.data().vwinners().v
|
||||
) | ranges::views::transform([](const auto &winner) {
|
||||
return Data::CreditsGiveawayOption::Winner{
|
||||
.users = winner.data().vusers().v,
|
||||
.perUserStars = winner.data().vper_user_stars().v,
|
||||
.isDefault = winner.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector,
|
||||
.storeProduct = qs(
|
||||
option.data().vstore_product().value_or_empty()),
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.credits = option.data().vstars().v,
|
||||
.yearlyBoosts = option.data().vyearly_boosts().v,
|
||||
.isExtended = option.data().is_extended(),
|
||||
.isDefault = option.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
};
|
||||
|
||||
_api.request(MTPpayments_GetStarsGiveawayOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -36,6 +36,22 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class CreditsGiveawayOptions final {
|
||||
public:
|
||||
CreditsGiveawayOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditsGiveawayOptions options() const;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
Data::CreditsGiveawayOptions _options;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsStatus final {
|
||||
public:
|
||||
CreditsStatus(not_null<PeerData*> peer);
|
||||
|
||||
@@ -115,6 +115,28 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
update(
|
||||
|
||||
@@ -49,6 +49,11 @@ public:
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
|
||||
@@ -67,7 +72,9 @@ private:
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height()),
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
MTPdouble(), // video_start_ts
|
||||
MTPstring())); // video_codec
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
|
||||
@@ -361,9 +361,10 @@ void Premium::resolveGiveawayInfo(
|
||||
? GiveawayState::Refunded
|
||||
: GiveawayState::Finished;
|
||||
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
|
||||
info.activatedCount = data.vactivated_count().v;
|
||||
info.activatedCount = data.vactivated_count().value_or_empty();
|
||||
info.finishDate = data.vfinish_date().v;
|
||||
info.startDate = data.vstart_date().v;
|
||||
info.credits = data.vstars_prize().value_or_empty();
|
||||
});
|
||||
_giveawayInfoDone(std::move(info));
|
||||
}).fail([=] {
|
||||
@@ -508,7 +509,9 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
invoice.creditsAmount
|
||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
consumer.put_done();
|
||||
@@ -537,16 +540,34 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.randomId = randomId,
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.amount = store.amount,
|
||||
.storeProduct = store.product,
|
||||
.randomId = randomId,
|
||||
.amount = store.amount,
|
||||
.storeQuantity = store.quantity,
|
||||
.users = token.users,
|
||||
.months = token.months,
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
||||
auto result = std::vector<GiftOptionData>();
|
||||
|
||||
if (!_optionsForOnePerson.currency.isEmpty()) {
|
||||
const auto count = int(_optionsForOnePerson.months.size());
|
||||
result.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
Assert(i < _optionsForOnePerson.totalCosts.size());
|
||||
result.push_back({
|
||||
.cost = _optionsForOnePerson.totalCosts[i],
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.months = _optionsForOnePerson.months[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
const auto it = _subscriptionOptions.find(amount);
|
||||
if (it != end(_subscriptionOptions)) {
|
||||
@@ -568,6 +589,41 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
}
|
||||
}
|
||||
|
||||
auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
-> rpl::producer<rpl::no_value, QString> {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
_api.request(MTPpayments_GetStarGifts(
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPpayments_StarGifts &result) {
|
||||
result.match([&](const MTPDpayments_starGifts &data) {
|
||||
_giftsHash = data.vhash().v;
|
||||
const auto &list = data.vgifts().v;
|
||||
const auto session = &_peer->session();
|
||||
auto gifts = std::vector<StarGift>();
|
||||
gifts.reserve(list.size());
|
||||
for (const auto &gift : list) {
|
||||
if (auto parsed = FromTL(session, gift)) {
|
||||
gifts.push_back(std::move(*parsed));
|
||||
}
|
||||
}
|
||||
_gifts = std::move(gifts);
|
||||
}, [&](const MTPDpayments_starGiftsNotModified &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
|
||||
return _gifts;
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const {
|
||||
constexpr auto kFallbackCount = 4;
|
||||
return _peer->session().appConfig().get<int>(
|
||||
@@ -702,4 +758,56 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
}) | rpl::take(1) | rpl::map(random));
|
||||
}
|
||||
|
||||
std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift) {
|
||||
const auto &data = gift.data();
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return {};
|
||||
}
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.convertStars = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
const auto &data = gift.data();
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
if (!parsed) {
|
||||
return {};
|
||||
}
|
||||
return UserStarGift{
|
||||
.gift = std::move(*parsed),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
session,
|
||||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.convertStars = int64(data.vconvert_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.anonymous = data.is_name_hidden(),
|
||||
.hidden = data.is_unsaved(),
|
||||
.mine = to->isSelf(),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -57,6 +57,7 @@ struct GiveawayInfo {
|
||||
TimeId tooEarlyDate = 0;
|
||||
TimeId finishDate = 0;
|
||||
TimeId startDate = 0;
|
||||
uint64 credits = 0;
|
||||
int winnersCount = 0;
|
||||
int activatedCount = 0;
|
||||
bool participating = false;
|
||||
@@ -66,6 +67,33 @@ struct GiveawayInfo {
|
||||
}
|
||||
};
|
||||
|
||||
struct GiftOptionData {
|
||||
int64 cost = 0;
|
||||
QString currency;
|
||||
int months = 0;
|
||||
};
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 convertStars = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift gift;
|
||||
TextWithEntities message;
|
||||
int64 convertStars = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool anonymous = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
||||
class Premium final {
|
||||
public:
|
||||
explicit Premium(not_null<ApiWrap*> api);
|
||||
@@ -170,6 +198,7 @@ public:
|
||||
PremiumGiftCodeOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] std::vector<GiftOptionData> optionsForPeer() const;
|
||||
[[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
|
||||
[[nodiscard]] const std::vector<int> &availablePresets() const;
|
||||
[[nodiscard]] int monthsFromPreset(int monthsIndex);
|
||||
@@ -186,6 +215,9 @@ public:
|
||||
[[nodiscard]] int giveawayPeriodMax() const;
|
||||
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
|
||||
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
|
||||
|
||||
private:
|
||||
struct Token final {
|
||||
int users = 0;
|
||||
@@ -205,7 +237,7 @@ private:
|
||||
base::flat_map<Amount, PremiumSubscriptionOptions> _subscriptionOptions;
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<float64> totalCosts;
|
||||
std::vector<int64> totalCosts;
|
||||
QString currency;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
@@ -213,6 +245,9 @@ private:
|
||||
|
||||
base::flat_map<Token, Store> _stores;
|
||||
|
||||
int32 _giftsHash = 0;
|
||||
std::vector<StarGift> _gifts;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
@@ -241,4 +276,11 @@ enum class RequirePremiumState {
|
||||
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -10,10 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_report.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/boxes/report_box_graphics.h"
|
||||
#include "ui/layers/show.h"
|
||||
|
||||
namespace Api {
|
||||
@@ -40,15 +41,11 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
|
||||
} // namespace
|
||||
|
||||
void SendReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data) {
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data) {
|
||||
auto done = [=] {
|
||||
show->showToast(tr::lng_report_thanks(tr::now));
|
||||
};
|
||||
@@ -58,18 +55,6 @@ void SendReport(
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](const MessageIdsList &ids) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(ids.size());
|
||||
for (const auto &fullId : ids) {
|
||||
apiIds.push_back(MTP_int(fullId.msg));
|
||||
}
|
||||
peer->session().api().request(MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
@@ -77,14 +62,93 @@ void SendReport(
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](StoryId id) {
|
||||
peer->session().api().request(MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(1, MTP_int(id)),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
});
|
||||
}
|
||||
|
||||
auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)> {
|
||||
using TLChoose = MTPDreportResultChooseOption;
|
||||
using TLAddComment = MTPDreportResultAddComment;
|
||||
using TLReported = MTPDreportResultReported;
|
||||
using Result = ReportResult;
|
||||
|
||||
struct State final {
|
||||
#ifdef _DEBUG
|
||||
~State() {
|
||||
qDebug() << "Messages or Stories Report ~State().";
|
||||
}
|
||||
#endif
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
|
||||
return [=](
|
||||
Data::ReportInput reportInput,
|
||||
Fn<void(Result)> done) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(reportInput.ids.size() + reportInput.stories.size());
|
||||
for (const auto &id : reportInput.ids) {
|
||||
apiIds.push_back(MTP_int(id));
|
||||
}
|
||||
for (const auto &story : reportInput.stories) {
|
||||
apiIds.push_back(MTP_int(story));
|
||||
}
|
||||
|
||||
const auto received = [=](
|
||||
const MTPReportResult &result,
|
||||
mtpRequestId requestId) {
|
||||
if (state->requestId != requestId) {
|
||||
return;
|
||||
}
|
||||
state->requestId = 0;
|
||||
done(result.match([&](const TLChoose &data) {
|
||||
const auto t = qs(data.vtitle());
|
||||
auto list = Result::Options();
|
||||
list.reserve(data.voptions().v.size());
|
||||
for (const auto &tl : data.voptions().v) {
|
||||
list.emplace_back(Result::Option{
|
||||
.id = tl.data().voption().v,
|
||||
.text = qs(tl.data().vtext()),
|
||||
});
|
||||
}
|
||||
return Result{ .options = std::move(list), .title = t };
|
||||
}, [&](const TLAddComment &data) -> Result {
|
||||
return {
|
||||
.commentOption = ReportResult::CommentOption{
|
||||
.optional = data.is_optional(),
|
||||
.id = data.voption().v,
|
||||
}
|
||||
};
|
||||
}, [&](const TLReported &data) -> Result {
|
||||
return { .successful = true };
|
||||
}));
|
||||
};
|
||||
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
state->requestId = 0;
|
||||
done({ .error = error.type() });
|
||||
};
|
||||
|
||||
if (!reportInput.stories.empty()) {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
} else {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class HistoryItem;
|
||||
class PeerData;
|
||||
class PhotoData;
|
||||
|
||||
@@ -15,17 +16,41 @@ class Show;
|
||||
enum class ReportReason;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
struct ReportInput;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct ReportResult final {
|
||||
using Id = QByteArray;
|
||||
struct Option final {
|
||||
Id id = 0;
|
||||
QString text;
|
||||
};
|
||||
using Options = std::vector<Option>;
|
||||
Options options;
|
||||
QString title;
|
||||
QString error;
|
||||
QString comment;
|
||||
struct CommentOption {
|
||||
bool optional = false;
|
||||
Id id = 0;
|
||||
};
|
||||
std::optional<CommentOption> commentOption;
|
||||
bool successful = false;
|
||||
};
|
||||
|
||||
void SendReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data);
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data);
|
||||
|
||||
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)>;
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -44,7 +44,10 @@ void InnerFillMessagePostFlags(
|
||||
if (ShouldSendSilent(peer, options)) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (!peer->amAnonymous()) {
|
||||
if (!peer->amAnonymous()
|
||||
|| (!peer->isBroadcast()
|
||||
&& options.sendAs
|
||||
&& options.sendAs != peer)) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
}
|
||||
const auto channel = peer->asBroadcast();
|
||||
@@ -544,7 +547,7 @@ void SendConfirmedFile(
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
@@ -569,7 +572,7 @@ void SendConfirmedFile(
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
|
||||
@@ -571,13 +571,22 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
_boostStatus.prepaidGiveaway = ranges::views::all(
|
||||
data.vprepaid_giveaways()->v
|
||||
) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.months = r.data().vmonths().v,
|
||||
.id = r.data().vid().v,
|
||||
.quantity = r.data().vquantity().v,
|
||||
.date = QDateTime::fromSecsSinceEpoch(
|
||||
r.data().vdate().v),
|
||||
};
|
||||
return r.match([&](const MTPDprepaidGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.months = data.vmonths().v,
|
||||
.quantity = data.vquantity().v,
|
||||
};
|
||||
}, [&](const MTPDprepaidStarsGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.credits = data.vstars().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.boosts = data.vboosts().v,
|
||||
};
|
||||
});
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
@@ -635,19 +644,21 @@ void Boosts::requestBoosts(
|
||||
}
|
||||
: Data::GiftCodeLink();
|
||||
list.push_back({
|
||||
data.is_gift(),
|
||||
data.is_giveaway(),
|
||||
data.is_unclaimed(),
|
||||
qs(data.vid()),
|
||||
data.vuser_id().value_or_empty(),
|
||||
data.vgiveaway_msg_id()
|
||||
.id = qs(data.vid()),
|
||||
.userId = UserId(data.vuser_id().value_or_empty()),
|
||||
.giveawayMessage = data.vgiveaway_msg_id()
|
||||
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
|
||||
: FullMsgId(),
|
||||
QDateTime::fromSecsSinceEpoch(data.vdate().v),
|
||||
QDateTime::fromSecsSinceEpoch(data.vexpires().v),
|
||||
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
|
||||
std::move(giftCodeLink),
|
||||
data.vmultiplier().value_or_empty(),
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.expiresAt = base::unixtime::parse(data.vexpires().v),
|
||||
.expiresAfterMonths = ((data.vexpires().v - data.vdate().v)
|
||||
/ kMonthsDivider),
|
||||
.giftCodeLink = std::move(giftCodeLink),
|
||||
.multiplier = data.vmultiplier().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.isGift = data.is_gift(),
|
||||
.isGiveaway = data.is_giveaway(),
|
||||
.isUnclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}
|
||||
done(Data::BoostsListSlice{
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_authorizations.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_ringtones.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
@@ -420,13 +421,12 @@ void Updates::channelDifferenceDone(
|
||||
"{ good - after not final channelDifference was received }%1"
|
||||
).arg(_session->mtp().isTestMode() ? " TESTMODE" : ""));
|
||||
getChannelDifference(channel);
|
||||
} else if (ranges::contains(
|
||||
_activeChats,
|
||||
channel,
|
||||
[](const auto &pair) { return pair.second.peer; })) {
|
||||
channel->ptsWaitingForShortPoll(timeout
|
||||
} else if (inActiveChats(channel)) {
|
||||
channel->ptsSetWaitingForShortPoll(timeout
|
||||
? (timeout * crl::time(1000))
|
||||
: kWaitForChannelGetDifference);
|
||||
} else {
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,6 +658,7 @@ void Updates::getDifferenceAfterFail() {
|
||||
wait = wait ? std::min(wait, i->second - now) : (i->second - now);
|
||||
++i;
|
||||
} else {
|
||||
i->first->ptsSetRequesting(false);
|
||||
getChannelDifference(i->first, ChannelDifferenceRequest::AfterFail);
|
||||
i = _whenGetDiffAfterFail.erase(i);
|
||||
}
|
||||
@@ -706,7 +707,9 @@ void Updates::getChannelDifference(
|
||||
_whenGetDiffByPts.remove(channel);
|
||||
}
|
||||
|
||||
if (!channel->ptsInited() || channel->ptsRequesting()) return;
|
||||
if (!channel->ptsInited() || channel->ptsRequesting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from != ChannelDifferenceRequest::AfterFail) {
|
||||
_whenGetDiffAfterFail.remove(channel);
|
||||
@@ -743,16 +746,32 @@ void Updates::addActiveChat(rpl::producer<PeerData*> chat) {
|
||||
std::move(
|
||||
chat
|
||||
) | rpl::start_with_next_done([=](PeerData *peer) {
|
||||
_activeChats[key].peer = peer;
|
||||
if (const auto channel = peer ? peer->asChannel() : nullptr) {
|
||||
channel->ptsWaitingForShortPoll(
|
||||
kWaitForChannelGetDifference);
|
||||
auto &active = _activeChats[key];
|
||||
const auto was = active.peer;
|
||||
if (was != peer) {
|
||||
active.peer = peer;
|
||||
if (const auto channel = was ? was->asChannel() : nullptr) {
|
||||
if (!inActiveChats(channel)) {
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
}
|
||||
}
|
||||
if (const auto channel = peer ? peer->asChannel() : nullptr) {
|
||||
channel->ptsSetWaitingForShortPoll(
|
||||
kWaitForChannelGetDifference);
|
||||
}
|
||||
}
|
||||
}, [=] {
|
||||
_activeChats.erase(key);
|
||||
}, _activeChats[key].lifetime);
|
||||
}
|
||||
|
||||
bool Updates::inActiveChats(not_null<PeerData*> peer) const {
|
||||
return ranges::contains(
|
||||
_activeChats,
|
||||
peer.get(),
|
||||
[](const auto &pair) { return pair.second.peer; });
|
||||
}
|
||||
|
||||
void Updates::requestChannelRangeDifference(not_null<History*> history) {
|
||||
Expects(history->peer->isChannel());
|
||||
|
||||
@@ -1561,6 +1580,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping new channel message because getting the difference."));
|
||||
return;
|
||||
}
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1653,6 +1673,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping channel message edit because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1668,6 +1689,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping pinned channel messages because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1782,6 +1804,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping delete channel messages because getting the difference."));
|
||||
return;
|
||||
}
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1845,6 +1868,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id());
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping channel web page update because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -2630,6 +2654,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
_session->credits().apply(data);
|
||||
} break;
|
||||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ public:
|
||||
void requestChannelRangeDifference(not_null<History*> history);
|
||||
|
||||
void addActiveChat(rpl::producer<PeerData*> chat);
|
||||
[[nodiscard]] bool inActiveChats(not_null<PeerData*> peer) const;
|
||||
|
||||
private:
|
||||
enum class ChannelDifferenceRequest {
|
||||
|
||||
@@ -214,7 +214,10 @@ struct State {
|
||||
|
||||
[[nodiscard]] QImage GenerateUserpic(Userpic &userpic, int size) {
|
||||
size *= style::DevicePixelRatio();
|
||||
auto result = userpic.peer->generateUserpicImage(userpic.view, size);
|
||||
auto result = PeerData::GenerateUserpicImage(
|
||||
userpic.peer,
|
||||
userpic.view,
|
||||
size);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
return result;
|
||||
}
|
||||
@@ -653,12 +656,12 @@ QString FormatReadDate(TimeId date, const QDateTime &now) {
|
||||
return tr::lng_mediaview_today(
|
||||
tr::now,
|
||||
lt_time,
|
||||
QLocale().toString(parsed.time(), QLocale::ShortFormat));
|
||||
QLocale().toString(parsed.time(), "HH:mm:ss"));
|
||||
} else if (readDate.addDays(1) == nowDate) {
|
||||
return tr::lng_mediaview_yesterday(
|
||||
tr::now,
|
||||
lt_time,
|
||||
QLocale().toString(parsed.time(), QLocale::ShortFormat));
|
||||
QLocale().toString(parsed.time(), "HH:mm:ss"));
|
||||
}
|
||||
return tr::lng_mediaview_date_time(
|
||||
tr::now,
|
||||
@@ -670,7 +673,7 @@ QString FormatReadDate(TimeId date, const QDateTime &now) {
|
||||
lt_day,
|
||||
QString::number(readDate.day())),
|
||||
lt_time,
|
||||
QLocale().toString(parsed.time(), QLocale::ShortFormat));
|
||||
QLocale().toString(parsed.time(), "HH:mm:ss"));
|
||||
}
|
||||
|
||||
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||
|
||||
@@ -2072,7 +2072,7 @@ void ApiWrap::deleteHistory(
|
||||
}
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (!justClear && !revoke) {
|
||||
channel->ptsWaitingForShortPoll(-1);
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
leaveChannel(channel);
|
||||
} else {
|
||||
if (const auto migrated = peer->migrateFrom()) {
|
||||
@@ -3661,9 +3661,7 @@ void ApiWrap::sendUploadedPhoto(
|
||||
Api::RemoteFileInfo info,
|
||||
Api::SendOptions options) {
|
||||
if (const auto item = _session->data().message(localId)) {
|
||||
// AyuGram useScheduledMessages
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (settings->useScheduledMessages && !options.scheduled) {
|
||||
if (AyuSettings::isUseScheduledMessages() && !options.scheduled) {
|
||||
auto current = base::unixtime::now();
|
||||
options.scheduled = current + 12;
|
||||
}
|
||||
@@ -3686,9 +3684,7 @@ void ApiWrap::sendUploadedDocument(
|
||||
return;
|
||||
}
|
||||
|
||||
// AyuGram useScheduledMessages
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (settings->useScheduledMessages && !options.scheduled) {
|
||||
if (AyuSettings::isUseScheduledMessages() && !options.scheduled) {
|
||||
auto current = base::unixtime::now();
|
||||
options.scheduled = current + 12;
|
||||
}
|
||||
@@ -3978,6 +3974,12 @@ void ApiWrap::sendBotStart(
|
||||
if (chat) {
|
||||
message.textWithTags.text += '@' + bot->username();
|
||||
}
|
||||
|
||||
if (AyuSettings::isUseScheduledMessages()) {
|
||||
auto current = base::unixtime::now();
|
||||
message.action.options.scheduled = current + 12;
|
||||
}
|
||||
|
||||
sendMessage(std::move(message));
|
||||
return;
|
||||
}
|
||||
@@ -4192,9 +4194,7 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Api::SendOptions options,
|
||||
uint64 randomId,
|
||||
Fn<void(bool)> done) {
|
||||
// AyuGram useScheduledMessages
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (settings->useScheduledMessages && !options.scheduled) {
|
||||
if (AyuSettings::isUseScheduledMessages() && !options.scheduled) {
|
||||
auto current = base::unixtime::now();
|
||||
options.scheduled = current + 12;
|
||||
}
|
||||
@@ -4237,8 +4237,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
MTP_vector<MTPInputMedia>(1, media),
|
||||
MTPstring()))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
@@ -4311,8 +4313,10 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias))),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias)),
|
||||
MTPstring()),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
@@ -4404,9 +4408,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
return;
|
||||
}
|
||||
|
||||
// AyuGram useScheduledMessages
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (settings->useScheduledMessages && !album->options.scheduled) {
|
||||
if (AyuSettings::isUseScheduledMessages() && !album->options.scheduled) {
|
||||
auto current = base::unixtime::now();
|
||||
album->options.scheduled = current + 12;
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
namespace AyuInfra {
|
||||
|
||||
void initLang() {
|
||||
QString langPackBaseId = Lang::GetInstance().baseId();
|
||||
QString langPackId = Lang::GetInstance().id();
|
||||
if (langPackId.isEmpty()) {
|
||||
LOG(("Lang ID not found! Re-use old language pack..."));
|
||||
QString id = Lang::GetInstance().id();
|
||||
QString baseId = Lang::GetInstance().baseId();
|
||||
if (id.isEmpty()) {
|
||||
LOG(("Language ID not found!"));
|
||||
return;
|
||||
}
|
||||
CustomLangPack::initInstance();
|
||||
CustomLangPack::currentInstance()->fetchCustomLangPack(langPackId, langPackBaseId);
|
||||
AyuLanguage::init();
|
||||
AyuLanguage::currentInstance()->fetchLanguage(id, baseId);
|
||||
}
|
||||
|
||||
void initFonts() {
|
||||
|
||||
@@ -20,49 +20,45 @@ std::map<QString, QString> langMapping = {
|
||||
{"zh-hant-raw", "zh-hant"},
|
||||
};
|
||||
|
||||
CustomLangPack *CustomLangPack::instance = nullptr;
|
||||
AyuLanguage *AyuLanguage::instance = nullptr;
|
||||
|
||||
CustomLangPack::CustomLangPack() = default;
|
||||
AyuLanguage::AyuLanguage() = default;
|
||||
|
||||
void CustomLangPack::initInstance() {
|
||||
if (!instance) instance = new CustomLangPack;
|
||||
void AyuLanguage::init() {
|
||||
if (!instance) instance = new AyuLanguage;
|
||||
}
|
||||
|
||||
CustomLangPack *CustomLangPack::currentInstance() {
|
||||
AyuLanguage *AyuLanguage::currentInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void CustomLangPack::fetchCustomLangPack(const QString &langPackId, const QString &langPackBaseId) {
|
||||
LOG(("Current Language pack ID: %1, Base ID: %2").arg(langPackId, langPackBaseId));
|
||||
void AyuLanguage::fetchLanguage(const QString &id, const QString &baseId) {
|
||||
auto finalLangPackId = langMapping.contains(id) ? langMapping[id] : id;
|
||||
|
||||
auto finalLangPackId = langMapping.contains(langPackId) ? langMapping[langPackId] : langPackId;
|
||||
|
||||
const auto proxy = Core::App().settings().proxy().isEnabled()
|
||||
? Core::App().settings().proxy().selected()
|
||||
: MTP::ProxyData();
|
||||
if (proxy.type == MTP::ProxyData::Type::Socks5 || proxy.type == MTP::ProxyData::Type::Http) {
|
||||
QNetworkProxy LocaleProxy = ToNetworkProxy(ToDirectIpProxy(proxy));
|
||||
networkManager.setProxy(LocaleProxy);
|
||||
if (Core::App().settings().proxy().isEnabled()) {
|
||||
const auto proxy = Core::App().settings().proxy().selected();
|
||||
if (proxy.type == MTP::ProxyData::Type::Socks5 || proxy.type == MTP::ProxyData::Type::Http) {
|
||||
const auto networkProxy = ToNetworkProxy(ToDirectIpProxy(Core::App().settings().proxy().selected()));
|
||||
networkManager.setProxy(networkProxy);
|
||||
}
|
||||
}
|
||||
|
||||
// using `jsdelivr` since China (...and maybe other?) users have some problems with GitHub
|
||||
// https://crowdin.com/project/ayugram/discussions/6
|
||||
QUrl url;
|
||||
if (!finalLangPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) {
|
||||
if (!finalLangPackId.isEmpty() && !baseId.isEmpty() && !needFallback) {
|
||||
url.setUrl(qsl("https://cdn.jsdelivr.net/gh/AyuGram/Languages@l10n_main/values/langs/%1/Shared.json").arg(
|
||||
finalLangPackId));
|
||||
} else {
|
||||
url.setUrl(qsl("https://cdn.jsdelivr.net/gh/AyuGram/Languages@l10n_main/values/langs/%1/Shared.json").arg(
|
||||
needFallback ? langPackBaseId : finalLangPackId));
|
||||
needFallback ? baseId : finalLangPackId));
|
||||
}
|
||||
_chkReply = networkManager.get(QNetworkRequest(url));
|
||||
connect(_chkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(fetchError(QNetworkReply::NetworkError)));
|
||||
connect(_chkReply, SIGNAL(finished()), this, SLOT(fetchFinished()));
|
||||
LOG(("Fetching %1 lang pack...").arg(
|
||||
needFallback ? (langPackBaseId.isEmpty() ? finalLangPackId : langPackBaseId) : finalLangPackId));
|
||||
}
|
||||
|
||||
void CustomLangPack::fetchFinished() {
|
||||
void AyuLanguage::fetchFinished() {
|
||||
if (!_chkReply) return;
|
||||
|
||||
QString langPackBaseId = Lang::GetInstance().baseId();
|
||||
@@ -70,72 +66,61 @@ void CustomLangPack::fetchFinished() {
|
||||
auto statusCode = _chkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (statusCode == 404 && !langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) {
|
||||
LOG(("AyuGram Language pack not found! Fallback to main language: %1...").arg(langPackBaseId));
|
||||
LOG(("AyuGram Language not found! Fallback to main language: %1...").arg(langPackBaseId));
|
||||
needFallback = true;
|
||||
_chkReply->disconnect();
|
||||
fetchCustomLangPack("", langPackBaseId);
|
||||
fetchLanguage("", langPackBaseId);
|
||||
} else {
|
||||
QByteArray result = _chkReply->readAll().trimmed();
|
||||
const auto result = _chkReply->readAll().trimmed();
|
||||
QJsonParseError error{};
|
||||
QJsonDocument str = QJsonDocument::fromJson(result, &error);
|
||||
const auto doc = QJsonDocument::fromJson(result, &error);
|
||||
if (error.error == QJsonParseError::NoError) {
|
||||
parseLangFile(str);
|
||||
applyLanguageJson(doc);
|
||||
} else {
|
||||
LOG(("Incorrect JSON File. Fallback to default language: English..."));
|
||||
loadDefaultLangFile();
|
||||
LOG(("Incorrect language JSON File."));
|
||||
}
|
||||
|
||||
_chkReply = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomLangPack::fetchError(QNetworkReply::NetworkError e) {
|
||||
void AyuLanguage::fetchError(QNetworkReply::NetworkError e) {
|
||||
LOG(("Network error: %1").arg(e));
|
||||
|
||||
if (e == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||
QString langPackBaseId = Lang::GetInstance().baseId();
|
||||
QString langPackId = Lang::GetInstance().id();
|
||||
const auto baseId = Lang::GetInstance().baseId();
|
||||
const auto id = Lang::GetInstance().id();
|
||||
|
||||
if (!langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) {
|
||||
LOG(("AyuGram Language pack not found! Fallback to main language: %1...").arg(langPackBaseId));
|
||||
if (!id.isEmpty() && !baseId.isEmpty() && !needFallback) {
|
||||
LOG(("AyuGram Language not found! Fallback to main language: %1...").arg(baseId));
|
||||
needFallback = true;
|
||||
_chkReply->disconnect();
|
||||
fetchCustomLangPack("", langPackBaseId);
|
||||
fetchLanguage("", baseId);
|
||||
} else {
|
||||
LOG(("AyuGram Language pack not found! Fallback to default language: English..."));
|
||||
loadDefaultLangFile();
|
||||
LOG(("AyuGram Language not found!"));
|
||||
_chkReply = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomLangPack::loadDefaultLangFile() {
|
||||
QFile file(":/localization/en.json");
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QJsonDocument str = QJsonDocument::fromJson(file.readAll());
|
||||
QJsonObject json = str.object();
|
||||
for (const QString &key : json.keys()) {
|
||||
Lang::GetInstance().applyValue(key.toUtf8(), json.value(key).toString().toUtf8());
|
||||
}
|
||||
Lang::GetInstance().updatePluralRules();
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void CustomLangPack::parseLangFile(QJsonDocument str) {
|
||||
QJsonObject json = str.object();
|
||||
void AyuLanguage::applyLanguageJson(QJsonDocument doc) {
|
||||
const auto json = doc.object();
|
||||
for (const QString &brokenKey : json.keys()) {
|
||||
auto key = qsl("ayu_") + brokenKey;
|
||||
auto val = json.value(brokenKey).toString().replace(qsl("&"), qsl("&")).toUtf8();
|
||||
const auto val = json.value(brokenKey).toString().replace(qsl("&"), qsl("&")).toUtf8();
|
||||
|
||||
if (key.endsWith("_zero") || key.endsWith("_two") || key.endsWith("_few") || key.endsWith("_many")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.endsWith("_one")) {
|
||||
key = key.replace("_one", "#one");
|
||||
} else if (key.endsWith("_other")) {
|
||||
key = key.replace("_other", "#other");
|
||||
}
|
||||
|
||||
Lang::GetInstance().resetValue(key.toUtf8());
|
||||
Lang::GetInstance().applyValue(key.toUtf8(), val);
|
||||
if (key.contains("#other")) {
|
||||
Lang::GetInstance().resetValue(key.toUtf8().replace("#other", "#few"));
|
||||
Lang::GetInstance().resetValue(key.toUtf8().replace("#other", "#few"));
|
||||
Lang::GetInstance().applyValue(key.toUtf8().replace("#other", "#few"), val);
|
||||
Lang::GetInstance().applyValue(key.toUtf8().replace("#other", "#many"), val);
|
||||
}
|
||||
}
|
||||
Lang::GetInstance().updatePluralRules();
|
||||
}
|
||||
|
||||
@@ -9,27 +9,26 @@
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtXml/QDomDocument>
|
||||
|
||||
class CustomLangPack : public QObject
|
||||
class AyuLanguage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(CustomLangPack)
|
||||
Q_DISABLE_COPY(AyuLanguage)
|
||||
|
||||
public:
|
||||
static CustomLangPack *currentInstance();
|
||||
static void initInstance();
|
||||
static CustomLangPack *instance;
|
||||
static AyuLanguage *currentInstance();
|
||||
static void init();
|
||||
static AyuLanguage *instance;
|
||||
|
||||
void fetchCustomLangPack(const QString &langPackId, const QString &langPackBaseId);
|
||||
void loadDefaultLangFile();
|
||||
void parseLangFile(QJsonDocument str);
|
||||
void fetchLanguage(const QString &id, const QString &baseId);
|
||||
void applyLanguageJson(QJsonDocument doc);
|
||||
|
||||
public Q_SLOTS:
|
||||
void fetchFinished();
|
||||
void fetchError(QNetworkReply::NetworkError e);
|
||||
|
||||
private:
|
||||
CustomLangPack();
|
||||
~CustomLangPack() override = default;
|
||||
AyuLanguage();
|
||||
~AyuLanguage() override = default;
|
||||
|
||||
QNetworkAccessManager networkManager;
|
||||
QNetworkReply *_chkReply = nullptr;
|
||||
|
||||
@@ -42,6 +42,7 @@ rpl::variable<QString> editedMarkReactive;
|
||||
rpl::variable<int> showPeerIdReactive;
|
||||
|
||||
rpl::variable<bool> hideFromBlockedReactive;
|
||||
rpl::event_stream<> historyUpdateReactive;
|
||||
|
||||
rpl::lifetime lifetime = rpl::lifetime();
|
||||
|
||||
@@ -203,11 +204,14 @@ AyuGramSettings::AyuGramSettings() {
|
||||
|
||||
markReadAfterAction = true;
|
||||
useScheduledMessages = false;
|
||||
sendWithoutSound = false;
|
||||
|
||||
// ~ Message edits & deletion history
|
||||
saveDeletedMessages = true;
|
||||
saveMessagesHistory = true;
|
||||
|
||||
saveForBots = false;
|
||||
|
||||
// ~ Message filters
|
||||
hideFromBlocked = false;
|
||||
|
||||
@@ -248,6 +252,15 @@ AyuGramSettings::AyuGramSettings() {
|
||||
showUserMessagesInContextMenu = 2;
|
||||
showMessageDetailsInContextMenu = 2;
|
||||
|
||||
showAttachButtonInMessageField = true;
|
||||
showCommandsButtonInMessageField = true;
|
||||
showEmojiButtonInMessageField = true;
|
||||
showMicrophoneButtonInMessageField = true;
|
||||
showAutoDeleteButtonInMessageField = true;
|
||||
|
||||
showAttachPopup = true;
|
||||
showEmojiPopup = true;
|
||||
|
||||
showLReadToggleInDrawer = false;
|
||||
showSReadToggleInDrawer = true;
|
||||
showGhostToggleInDrawer = true;
|
||||
@@ -331,6 +344,10 @@ void AyuGramSettings::set_useScheduledMessages(bool val) {
|
||||
useScheduledMessages = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_sendWithoutSound(bool val) {
|
||||
sendWithoutSound = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_saveDeletedMessages(bool val) {
|
||||
saveDeletedMessages = val;
|
||||
}
|
||||
@@ -339,6 +356,10 @@ void AyuGramSettings::set_saveMessagesHistory(bool val) {
|
||||
saveMessagesHistory = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_saveForBots(bool val) {
|
||||
saveForBots = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_hideFromBlocked(bool val) {
|
||||
hideFromBlocked = val;
|
||||
hideFromBlockedReactive = val;
|
||||
@@ -426,6 +447,41 @@ void AyuGramSettings::set_showMessageDetailsInContextMenu(int val) {
|
||||
showMessageDetailsInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showAttachButtonInMessageField(bool val) {
|
||||
showAttachButtonInMessageField = val;
|
||||
triggerHistoryUpdate();
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showCommandsButtonInMessageField(bool val) {
|
||||
showCommandsButtonInMessageField = val;
|
||||
triggerHistoryUpdate();
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showEmojiButtonInMessageField(bool val) {
|
||||
showEmojiButtonInMessageField = val;
|
||||
triggerHistoryUpdate();
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMicrophoneButtonInMessageField(bool val) {
|
||||
showMicrophoneButtonInMessageField = val;
|
||||
triggerHistoryUpdate();
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showAutoDeleteButtonInMessageField(bool val) {
|
||||
showAutoDeleteButtonInMessageField = val;
|
||||
triggerHistoryUpdate();
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showAttachPopup(bool val) {
|
||||
showAttachPopup = val;
|
||||
triggerHistoryUpdate();
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showEmojiPopup(bool val) {
|
||||
showEmojiPopup = val;
|
||||
triggerHistoryUpdate();
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showLReadToggleInDrawer(bool val) {
|
||||
showLReadToggleInDrawer = val;
|
||||
}
|
||||
@@ -495,7 +551,12 @@ void AyuGramSettings::set_voiceConfirmation(bool val) {
|
||||
voiceConfirmation = val;
|
||||
}
|
||||
|
||||
bool get_ghostModeEnabled() {
|
||||
bool isUseScheduledMessages() {
|
||||
const auto settings = &getInstance();
|
||||
return isGhostModeActive() && settings->useScheduledMessages;
|
||||
}
|
||||
|
||||
bool isGhostModeActive() {
|
||||
return ghostModeEnabled.current();
|
||||
}
|
||||
|
||||
@@ -519,4 +580,12 @@ rpl::producer<bool> get_hideFromBlockedReactive() {
|
||||
return hideFromBlockedReactive.value();
|
||||
}
|
||||
|
||||
void triggerHistoryUpdate() {
|
||||
historyUpdateReactive.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> get_historyUpdateReactive() {
|
||||
return historyUpdateReactive.events();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,10 +25,13 @@ public:
|
||||
|
||||
bool markReadAfterAction;
|
||||
bool useScheduledMessages;
|
||||
bool sendWithoutSound;
|
||||
|
||||
bool saveDeletedMessages;
|
||||
bool saveMessagesHistory;
|
||||
|
||||
bool saveForBots;
|
||||
|
||||
bool hideFromBlocked;
|
||||
|
||||
bool disableAds;
|
||||
@@ -56,6 +59,15 @@ public:
|
||||
int showUserMessagesInContextMenu;
|
||||
int showMessageDetailsInContextMenu;
|
||||
|
||||
bool showAttachButtonInMessageField;
|
||||
bool showCommandsButtonInMessageField;
|
||||
bool showEmojiButtonInMessageField;
|
||||
bool showMicrophoneButtonInMessageField;
|
||||
bool showAutoDeleteButtonInMessageField;
|
||||
|
||||
bool showAttachPopup;
|
||||
bool showEmojiPopup;
|
||||
|
||||
bool showLReadToggleInDrawer;
|
||||
bool showSReadToggleInDrawer;
|
||||
bool showGhostToggleInDrawer;
|
||||
@@ -90,10 +102,13 @@ public:
|
||||
|
||||
void set_markReadAfterAction(bool val);
|
||||
void set_useScheduledMessages(bool val);
|
||||
void set_sendWithoutSound(bool val);
|
||||
|
||||
void set_saveDeletedMessages(bool val);
|
||||
void set_saveMessagesHistory(bool val);
|
||||
|
||||
void set_saveForBots(bool val);
|
||||
|
||||
void set_hideFromBlocked(bool val);
|
||||
|
||||
void set_disableAds(bool val);
|
||||
@@ -121,6 +136,15 @@ public:
|
||||
void set_showUserMessagesInContextMenu(int val);
|
||||
void set_showMessageDetailsInContextMenu(int val);
|
||||
|
||||
void set_showAttachButtonInMessageField(bool val);
|
||||
void set_showCommandsButtonInMessageField(bool val);
|
||||
void set_showEmojiButtonInMessageField(bool val);
|
||||
void set_showMicrophoneButtonInMessageField(bool val);
|
||||
void set_showAutoDeleteButtonInMessageField(bool val);
|
||||
|
||||
void set_showAttachPopup(bool val);
|
||||
void set_showEmojiPopup(bool val);
|
||||
|
||||
void set_showLReadToggleInDrawer(bool val);
|
||||
void set_showSReadToggleInDrawer(bool val);
|
||||
void set_showGhostToggleInDrawer(bool val);
|
||||
@@ -155,8 +179,10 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
sendOfflinePacketAfterOnline,
|
||||
markReadAfterAction,
|
||||
useScheduledMessages,
|
||||
sendWithoutSound,
|
||||
saveDeletedMessages,
|
||||
saveMessagesHistory,
|
||||
saveForBots,
|
||||
hideFromBlocked,
|
||||
disableAds,
|
||||
disableStories,
|
||||
@@ -178,6 +204,13 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
showHideMessageInContextMenu,
|
||||
showUserMessagesInContextMenu,
|
||||
showMessageDetailsInContextMenu,
|
||||
showAttachButtonInMessageField,
|
||||
showCommandsButtonInMessageField,
|
||||
showEmojiButtonInMessageField,
|
||||
showMicrophoneButtonInMessageField,
|
||||
showAutoDeleteButtonInMessageField,
|
||||
showAttachPopup,
|
||||
showEmojiPopup,
|
||||
showLReadToggleInDrawer,
|
||||
showSReadToggleInDrawer,
|
||||
showGhostToggleInDrawer,
|
||||
@@ -207,9 +240,14 @@ rpl::producer<QString> get_editedMarkReactive();
|
||||
|
||||
rpl::producer<int> get_showPeerIdReactive();
|
||||
|
||||
bool get_ghostModeEnabled();
|
||||
bool isGhostModeActive();
|
||||
bool isUseScheduledMessages();
|
||||
|
||||
rpl::producer<bool> get_ghostModeEnabledReactive();
|
||||
|
||||
rpl::producer<bool> get_hideFromBlockedReactive();
|
||||
|
||||
void triggerHistoryUpdate();
|
||||
rpl::producer<> get_historyUpdateReactive();
|
||||
|
||||
}
|
||||
|
||||
@@ -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,21 +100,22 @@ 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),
|
||||
make_column("enabled", &RegexFilter::enabled),
|
||||
make_column("reversed", &RegexFilter::reversed),
|
||||
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 +123,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 +136,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 +191,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 +214,54 @@ 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 userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit) {
|
||||
return storage.get_all<DeletedMessage>(
|
||||
where(
|
||||
column<DeletedMessage>(&DeletedMessage::userId) == userId and
|
||||
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
|
||||
(column<DeletedMessage>(&DeletedMessage::topicId) == topicId or topicId == 0) 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 userId, ID dialogId, ID topicId) {
|
||||
try {
|
||||
return storage.count<DeletedMessage>(
|
||||
where(
|
||||
column<DeletedMessage>(&DeletedMessage::userId) == userId and
|
||||
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
|
||||
(column<DeletedMessage>(&DeletedMessage::topicId) == topicId or topicId == 0)
|
||||
)
|
||||
) > 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 userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit);
|
||||
bool hasDeletedMessages(ID userId, ID dialogId, ID topicId);
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#define ID long long
|
||||
|
||||
template<typename TableName>
|
||||
class AyuMessageBase
|
||||
{
|
||||
public:
|
||||
@@ -31,12 +30,14 @@ public:
|
||||
std::string fwdName;
|
||||
int fwdDate;
|
||||
std::string fwdPostAuthor;
|
||||
std::string postAuthor;
|
||||
int replyFlags;
|
||||
int replyMessageId;
|
||||
ID replyPeerId;
|
||||
int replyTopId;
|
||||
bool replyForumTopic;
|
||||
std::vector<char> replySerialized;
|
||||
std::vector<char> replyMarkupSerialized;
|
||||
int entityCreateDate;
|
||||
std::string text;
|
||||
std::vector<char> textEntities;
|
||||
@@ -49,9 +50,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
|
||||
{
|
||||
@@ -73,6 +78,7 @@ public:
|
||||
std::vector<char> id;
|
||||
std::string text;
|
||||
bool enabled;
|
||||
bool reversed;
|
||||
bool caseInsensitive;
|
||||
std::unique_ptr<ID> dialogId; // nullable
|
||||
};
|
||||
|
||||
@@ -24,27 +24,54 @@
|
||||
|
||||
namespace AyuMessages {
|
||||
|
||||
void map(HistoryMessageEdition &edition, not_null<HistoryItem*> item, EditedMessage &message) {
|
||||
message.userId = item->history()->owner().session().userId().bare;
|
||||
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) {
|
||||
const ID userId = item->history()->owner().session().userId().bare & PeerId::kChatTypeMask;
|
||||
|
||||
message.userId = userId;
|
||||
message.dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||
message.groupedId = item->groupId().value;
|
||||
message.peerId = item->from()->id.value; // todo: ???
|
||||
message.fromId = item->from()->id.value;
|
||||
if (auto topic = item->history()->asTopic()) {
|
||||
message.topicId = topic->rootId().bare;
|
||||
message.groupedId = item->groupId().raw();
|
||||
message.peerId = item->history()->peer->id.value & PeerId::kChatTypeMask;
|
||||
message.fromId = item->from()->id.value & PeerId::kChatTypeMask;
|
||||
if (item->topic()) {
|
||||
message.topicId = item->topicRootId().bare;
|
||||
}
|
||||
message.messageId = item->id.bare;
|
||||
message.date = item->date();
|
||||
message.flags = AyuMapper::mapItemFlagsToMTPFlags(item);
|
||||
|
||||
if (auto edited = item->Get<HistoryMessageEdited>()) {
|
||||
if (const auto edited = item->Get<HistoryMessageEdited>()) {
|
||||
message.editDate = edited->date;
|
||||
} else {
|
||||
message.editDate = base::unixtime::now();
|
||||
}
|
||||
|
||||
message.views = item->viewsCount();
|
||||
message.entityCreateDate = base::unixtime::now(); // todo: rework
|
||||
message.fwdFlags = 0;
|
||||
message.fwdFromId = 0;
|
||||
// message.fwdName
|
||||
message.fwdDate = 0;
|
||||
// message.fwdPostAuthor
|
||||
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
|
||||
message.postAuthor = msgsigned->author.toStdString();
|
||||
}
|
||||
message.replyFlags = 0;
|
||||
message.replyMessageId = 0;
|
||||
message.replyPeerId = 0;
|
||||
message.replyTopId = 0;
|
||||
message.replyForumTopic = false;
|
||||
// message.replySerialized
|
||||
// message.replyMarkupSerialized
|
||||
message.entityCreateDate = base::unixtime::now();
|
||||
|
||||
auto serializedText = AyuMapper::serializeTextWithEntities(item);
|
||||
message.text = serializedText.first;
|
||||
@@ -52,35 +79,62 @@ void map(HistoryMessageEdition &edition, not_null<HistoryItem*> item, EditedMess
|
||||
|
||||
// todo: implement mapping
|
||||
message.mediaPath = "/";
|
||||
// message.hqThumbPath
|
||||
message.documentType = DOCUMENT_TYPE_NONE;
|
||||
|
||||
// message.documentSerialized;
|
||||
// message.thumbsSerialized;
|
||||
// message.documentAttributesSerialized;
|
||||
// message.mimeType;
|
||||
// message.documentSerialized
|
||||
// message.thumbsSerialized
|
||||
// message.documentAttributesSerialized
|
||||
// message.mimeType
|
||||
}
|
||||
|
||||
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item) {
|
||||
void addEditedMessage(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) {
|
||||
auto userId = item->history()->owner().session().userId().bare;
|
||||
auto dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||
auto msgId = item->id.bare;
|
||||
std::vector<AyuMessageBase> getEditedMessages(not_null<HistoryItem*> item, ID minId, ID maxId, int totalLimit) {
|
||||
const ID userId = item->history()->owner().session().userId().bare & PeerId::kChatTypeMask;
|
||||
const auto dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||
const 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) {
|
||||
auto userId = item->history()->owner().session().userId().bare;
|
||||
auto dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||
auto msgId = item->id.bare;
|
||||
const ID userId = item->history()->owner().session().userId().bare & PeerId::kChatTypeMask;
|
||||
const auto dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||
const auto msgId = item->id.bare;
|
||||
|
||||
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 topicId, ID minId, ID maxId, int totalLimit) {
|
||||
const ID userId = peer->session().userId().bare & PeerId::kChatTypeMask;
|
||||
return convertToBase(
|
||||
AyuDatabase::getDeletedMessages(userId, getDialogIdFromPeer(peer), topicId, minId, maxId, totalLimit));
|
||||
}
|
||||
|
||||
bool hasDeletedMessages(not_null<PeerData*> peer, ID topicId) {
|
||||
const ID userId = peer->session().userId().bare & PeerId::kChatTypeMask;
|
||||
return AyuDatabase::hasDeletedMessages(userId, getDialogIdFromPeer(peer), topicId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,8 +12,12 @@
|
||||
|
||||
namespace AyuMessages {
|
||||
|
||||
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item);
|
||||
std::vector<EditedMessage> getEditedMessages(not_null<HistoryItem*> item);
|
||||
void addEditedMessage(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 topicId, ID minId, ID maxId, int totalLimit);
|
||||
bool hasDeletedMessages(not_null<PeerData*> peer, ID topicId);
|
||||
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ QImage addPadding(const QImage &original) {
|
||||
original.height() + 2 * st::messageShotPadding,
|
||||
QImage::Format_ARGB32_Premultiplied
|
||||
);
|
||||
paddedImage.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
paddedImage.fill(Qt::transparent);
|
||||
|
||||
Painter painter(&paddedImage);
|
||||
@@ -351,11 +352,11 @@ QImage Make(not_null<QWidget*> box, const ShotConfig &config) {
|
||||
height += view->resizeGetHeight(width);
|
||||
}
|
||||
|
||||
// width *= style::DevicePixelRatio();
|
||||
height *= style::DevicePixelRatio();
|
||||
|
||||
// create the image
|
||||
QImage image(width, height, QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
|
||||
const auto viewport = QRect(0, 0, width, height);
|
||||
@@ -408,15 +409,6 @@ QImage Make(not_null<QWidget*> box, const ShotConfig &config) {
|
||||
picY,
|
||||
width,
|
||||
st::msgPhotoSize);
|
||||
} else {
|
||||
auto &userpic = hiddenSenderUserpics[message->id];
|
||||
const auto valid = info->paintCustomUserpic(
|
||||
p,
|
||||
userpic,
|
||||
picX,
|
||||
picY,
|
||||
width,
|
||||
st::msgPhotoSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,6 +424,7 @@ QImage Make(not_null<QWidget*> box, const ShotConfig &config) {
|
||||
}
|
||||
|
||||
auto newResult = QImage(result.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
newResult.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
newResult.fill(makeDefaultBackgroundColor());
|
||||
|
||||
Painter painter(&newResult);
|
||||
|
||||
@@ -13,14 +13,12 @@
|
||||
namespace AyuFeatures::StreamerMode::Impl {
|
||||
|
||||
void enableHook() {
|
||||
auto handle = Core::App().activeWindow()->widget()->psHwnd();
|
||||
Core::App().enumerateWindows([&](not_null<Window::Controller*> w) {
|
||||
SetWindowDisplayAffinity(w->widget()->psHwnd(), WDA_EXCLUDEFROMCAPTURE);
|
||||
});
|
||||
}
|
||||
|
||||
void disableHook() {
|
||||
auto handle = Core::App().activeWindow()->widget()->psHwnd();
|
||||
Core::App().enumerateWindows([&](not_null<Window::Controller*> w) {
|
||||
SetWindowDisplayAffinity(w->widget()->psHwnd(), WDA_NONE);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,6 +19,12 @@ ayuStreamerModeMenuIcon: icon {{ "ayu/streamer", menuIconColor }};
|
||||
|
||||
ayuMenuIconToBeginning: icon {{ "ayu/to_beginning", menuIconColor }};
|
||||
|
||||
messageFieldAttachIcon: icon {{ "ayu/message_field/attach", menuIconColor }};
|
||||
messageFieldCommandsIcon: icon {{ "ayu/message_field/commands", menuIconColor }};
|
||||
messageFieldEmojiIcon: icon {{ "ayu/message_field/emoji", menuIconColor }};
|
||||
messageFieldVoiceIcon: icon {{ "ayu/message_field/voice", menuIconColor }};
|
||||
messageFieldTTLIcon: icon {{ "ayu/message_field/ttl", menuIconColor }};
|
||||
|
||||
inChannelBadgeIcon: icon {{ "ayu/channel", msgInDateFg }};
|
||||
inChannelBadgeSelectedIcon: icon {{ "ayu/channel", msgInDateFgSelected }};
|
||||
outChannelBadgeIcon: icon {{ "ayu/channel", msgOutDateFg }};
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <ayu/ayu_settings.h>
|
||||
|
||||
#include "ayu/ayu_fonts.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
struct Font
|
||||
{
|
||||
|
||||
@@ -6,27 +6,32 @@
|
||||
// Copyright @Radolyn, 2024
|
||||
#include "ayu/ui/context_menu/context_menu.h"
|
||||
|
||||
#include <styles/style_menu_icons.h>
|
||||
|
||||
#include "apiwrap.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 "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "window/window_session_controller.h"
|
||||
@@ -37,13 +42,119 @@ bool needToShowItem(int state) {
|
||||
return state == 1 || (state == 2 && base::IsExtendedContextMenuModifierPressed());
|
||||
}
|
||||
|
||||
void AddDeletedMessagesActions(PeerData *peerData,
|
||||
Data::Thread *thread,
|
||||
not_null<Window::SessionController*> sessionController,
|
||||
const Window::PeerMenuCallback &addCallback) {
|
||||
if (!peerData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto topic = peerData->isForum() ? thread->asTopic() : nullptr;
|
||||
const auto topicId = topic ? topic->rootId().bare : 0;
|
||||
|
||||
const auto has = AyuMessages::hasDeletedMessages(peerData, topicId);
|
||||
if (!has) {
|
||||
return;
|
||||
}
|
||||
|
||||
addCallback(tr::ayu_ViewDeletedMenuText(tr::now),
|
||||
[=]
|
||||
{
|
||||
sessionController->session().tryResolveWindow()
|
||||
->showSection(std::make_shared<MessageHistory::SectionMemento>(peerData, nullptr, topicId));
|
||||
},
|
||||
&st::menuIconArchive);
|
||||
}
|
||||
|
||||
void AddJumpToBeginningAction(PeerData *peerData,
|
||||
Data::Thread *thread,
|
||||
not_null<Window::SessionController*> sessionController,
|
||||
const Window::PeerMenuCallback &addCallback) {
|
||||
const auto user = peerData->asUser();
|
||||
const auto group = peerData->isChat() ? peerData->asChat() : nullptr;
|
||||
const auto chat = peerData->isMegagroup()
|
||||
? peerData->asMegagroup()
|
||||
: peerData->isChannel()
|
||||
? peerData->asChannel()
|
||||
: nullptr;
|
||||
const auto topic = peerData->isForum() ? thread->asTopic() : nullptr;
|
||||
if (!user && !group && !chat && !topic) {
|
||||
return;
|
||||
}
|
||||
if (topic && topic->creating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto controller = sessionController;
|
||||
const auto jumpToDate = [=](auto history, auto callback)
|
||||
{
|
||||
const auto weak = base::make_weak(controller);
|
||||
controller->session().api().resolveJumpToDate(
|
||||
history,
|
||||
QDate(2013, 8, 1),
|
||||
[=](not_null<PeerData*> peer, MsgId id)
|
||||
{
|
||||
if (const auto strong = weak.get()) {
|
||||
callback(peer, id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto showPeerHistory = [=](auto peer, MsgId id)
|
||||
{
|
||||
controller->showPeerHistory(
|
||||
peer,
|
||||
Window::SectionShow::Way::Forward,
|
||||
id);
|
||||
};
|
||||
|
||||
const auto showTopic = [=](auto topic, MsgId id)
|
||||
{
|
||||
controller->showTopic(
|
||||
topic,
|
||||
id,
|
||||
Window::SectionShow::Way::Forward);
|
||||
};
|
||||
|
||||
addCallback(
|
||||
tr::ayu_JumpToBeginning(tr::now),
|
||||
[=]
|
||||
{
|
||||
if (user) {
|
||||
jumpToDate(controller->session().data().history(user), showPeerHistory);
|
||||
} else if (group && !chat) {
|
||||
jumpToDate(controller->session().data().history(group), showPeerHistory);
|
||||
} else if (chat && !topic) {
|
||||
if (!chat->migrateFrom() && chat->availableMinId() == 1) {
|
||||
showPeerHistory(chat, 1);
|
||||
} else {
|
||||
jumpToDate(controller->session().data().history(chat), showPeerHistory);
|
||||
}
|
||||
} else if (topic) {
|
||||
if (topic->isGeneral()) {
|
||||
showTopic(topic, 1);
|
||||
} else {
|
||||
jumpToDate(
|
||||
topic,
|
||||
[=](not_null<PeerData*>, MsgId id)
|
||||
{
|
||||
showTopic(topic, id);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
&st::ayuMenuIconToBeginning);
|
||||
}
|
||||
|
||||
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, 0));
|
||||
},
|
||||
&st::ayuEditsHistoryIcon);
|
||||
}
|
||||
@@ -90,7 +201,7 @@ void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
? Dialogs::Key{item->topic()}
|
||||
: Dialogs::Key{item->history()}
|
||||
: Dialogs::Key();
|
||||
mainWidget->content()->searchMessages(QString(), key, item->from()->asUser());
|
||||
mainWidget->content()->searchMessages(QString(), key, item->from());
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -120,7 +231,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;
|
||||
@@ -310,7 +421,7 @@ void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
}
|
||||
|
||||
void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
if (item->isLocal() || item->isService()) {
|
||||
if (item->isLocal() || item->isService() || item->out() || item->isDeleted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,6 +438,25 @@ void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
[=]()
|
||||
{
|
||||
readHistory(item);
|
||||
if (item->media() && !item->media()->ttlSeconds()) {
|
||||
const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id));
|
||||
if (const auto channel = item->history()->peer->asChannel()) {
|
||||
item->history()->session().api().request(MTPchannels_ReadMessageContents(
|
||||
channel->inputChannel,
|
||||
ids
|
||||
)).send();
|
||||
} else {
|
||||
item->history()->session().api().request(MTPmessages_ReadMessageContents(
|
||||
ids
|
||||
)).done([=](const MTPmessages_AffectedMessages &result)
|
||||
{
|
||||
item->history()->session().api().applyAffectedMessages(
|
||||
item->history()->peer,
|
||||
result);
|
||||
}).send();
|
||||
}
|
||||
item->markContentsRead();
|
||||
}
|
||||
},
|
||||
&st::menuIconShowInChat);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,23 @@
|
||||
|
||||
#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,
|
||||
Data::Thread *thread,
|
||||
not_null<Window::SessionController*> sessionController,
|
||||
const Window::PeerMenuCallback &addCallback);
|
||||
|
||||
void AddJumpToBeginningAction(PeerData *peerData,
|
||||
Data::Thread *thread,
|
||||
not_null<Window::SessionController*> sessionController,
|
||||
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"
|
||||
@@ -57,17 +57,17 @@
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace EditedLog {
|
||||
namespace {
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
// If we require to support more admins we'll have to rewrite this anyway.
|
||||
constexpr auto kMaxChannelAdmins = 200;
|
||||
namespace MessageHistory {
|
||||
namespace {
|
||||
|
||||
constexpr auto kScrollDateHideTimeout = 1000;
|
||||
|
||||
constexpr auto kEventsFirstPage = 20;
|
||||
constexpr auto kMessagesFirstPage = 20;
|
||||
|
||||
constexpr auto kEventsPerPage = 50;
|
||||
constexpr auto kMessagesPerPage = 30;
|
||||
|
||||
constexpr auto kClearUserpicsAfter = 50;
|
||||
|
||||
@@ -247,11 +247,13 @@ InnerWidget::InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item)
|
||||
HistoryItem *item,
|
||||
ID topicId)
|
||||
: RpWidget(parent),
|
||||
_controller(controller),
|
||||
_peer(peer),
|
||||
_item(item),
|
||||
_topicId(topicId),
|
||||
_history(peer->owner().history(peer)),
|
||||
_api(&_peer->session().mtp()),
|
||||
_pathGradient(
|
||||
@@ -331,7 +333,6 @@ InnerWidget::InnerWidget(
|
||||
const Data::Session::ItemVisibilityQuery &query)
|
||||
{
|
||||
return (_history == query.item->history())
|
||||
&& query.item->isAdminLogEntry()
|
||||
&& isVisible();
|
||||
}) | rpl::start_with_next([=](
|
||||
const Data::Session::ItemVisibilityQuery &query)
|
||||
@@ -378,9 +379,7 @@ void InnerWidget::visibleTopBottomUpdated(
|
||||
}
|
||||
|
||||
updateVisibleTopItem();
|
||||
if (_items.size() == 0) {
|
||||
addEvents(Direction::Up);
|
||||
}
|
||||
checkPreloadMore();
|
||||
if (scrolledUp) {
|
||||
_scrollDateCheck.call();
|
||||
} else {
|
||||
@@ -463,17 +462,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 {
|
||||
@@ -523,8 +522,8 @@ bool InnerWidget::elementUnderCursor(
|
||||
return (Element::Hovered() == view);
|
||||
}
|
||||
|
||||
bool InnerWidget::elementInSelectionMode() {
|
||||
return false;
|
||||
HistoryView::SelectionModeResult InnerWidget::elementInSelectionMode() {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool InnerWidget::elementIntersectsRange(
|
||||
@@ -611,7 +610,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) {
|
||||
@@ -631,17 +630,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.
|
||||
}
|
||||
|
||||
@@ -651,23 +648,63 @@ 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;
|
||||
updateMinMaxIds();
|
||||
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, _topicId, 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,
|
||||
@@ -676,19 +713,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) {
|
||||
@@ -718,6 +786,7 @@ void InnerWidget::updateSize() {
|
||||
TWidget::resizeToWidth(width());
|
||||
restoreScrollPosition();
|
||||
updateVisibleTopItem();
|
||||
checkPreloadMore();
|
||||
}
|
||||
|
||||
int InnerWidget::resizeGetHeight(int newWidth) {
|
||||
@@ -1103,6 +1172,13 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
saveDocumentToFile(lnkDocument);
|
||||
}),
|
||||
&st::menuIconDownload);
|
||||
|
||||
HistoryView::AddCopyFilename(
|
||||
_menu,
|
||||
lnkDocument,
|
||||
[] { return false; });
|
||||
|
||||
|
||||
if (lnkDocument->hasAttachedStickers()) {
|
||||
const auto controller = _controller;
|
||||
auto callback = [=]
|
||||
@@ -1634,4 +1710,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,8 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item);
|
||||
HistoryItem *item,
|
||||
ID topicId);
|
||||
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
@@ -62,7 +63,7 @@ public:
|
||||
|
||||
[[nodiscard]] rpl::producer<int> scrollToSignal() const;
|
||||
|
||||
[[nodiscard]] not_null<PeerData*> channel() const {
|
||||
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ public:
|
||||
HistoryView::Context elementContext() override;
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
bool elementInSelectionMode() override;
|
||||
HistoryView::SelectionModeResult elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
int from,
|
||||
@@ -207,15 +208,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 +251,8 @@ private:
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<HistoryItem*> _item;
|
||||
HistoryItem *_item;
|
||||
ID _topicId;
|
||||
const not_null<History*> _history;
|
||||
MTP::Sender _api;
|
||||
|
||||
@@ -255,7 +260,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 +284,13 @@ private:
|
||||
Element *_scrollDateLastItem = nullptr;
|
||||
int _scrollDateLastItemTop = 0;
|
||||
|
||||
// Up - max, Down - min.
|
||||
ID _maxId = 0;
|
||||
ID _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 +316,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace EditedLog
|
||||
} // namespace MessageHistory
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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));
|
||||
}
|
||||
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 makeSimpleTextMessage = [&](TextWithEntities &&text)
|
||||
{
|
||||
base::flags<MessageFlag> flags = MessageFlag::AdminLogEntry;
|
||||
if (from) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
} else {
|
||||
flags |= MessageFlag::HasPostAuthor;
|
||||
}
|
||||
if (!message.postAuthor.empty()) {
|
||||
flags |= MessageFlag::HasPostAuthor;
|
||||
}
|
||||
|
||||
return history->makeMessage({
|
||||
.id = history->nextNonHistoryEntryId(),
|
||||
.flags = flags,
|
||||
.from = from ? from->id : 0,
|
||||
.date = date,
|
||||
.postAuthor = !message.postAuthor.empty()
|
||||
? QString::fromStdString(message.postAuthor)
|
||||
: from
|
||||
? QString()
|
||||
: QString("unknown user: %1").arg(message.fromId),
|
||||
},
|
||||
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
|
||||
{
|
||||
@@ -63,7 +63,7 @@ object_ptr<Window::SectionWidget> SectionMemento::createWidget(
|
||||
if (column == Window::Column::Third) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = object_ptr<Widget>(parent, controller, _peer, _item);
|
||||
auto result = object_ptr<Widget>(parent, controller, _peer, _item, _topicId);
|
||||
result->setInternalState(geometry, this);
|
||||
return result;
|
||||
}
|
||||
@@ -142,12 +142,14 @@ Widget::Widget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item)
|
||||
HistoryItem *item,
|
||||
ID topicId)
|
||||
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(peer)),
|
||||
_scroll(this, st::historyScroll, false),
|
||||
_fixedBar(this, controller, peer),
|
||||
_fixedBarShadow(this),
|
||||
_item(item) {
|
||||
_item(item),
|
||||
_topicId(topicId) {
|
||||
_fixedBar->move(0, 0);
|
||||
_fixedBar->resizeToWidth(width());
|
||||
_fixedBar->show();
|
||||
@@ -161,7 +163,7 @@ Widget::Widget(
|
||||
},
|
||||
lifetime());
|
||||
|
||||
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, peer, item));
|
||||
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, peer, item, topicId));
|
||||
_inner->scrollToSignal(
|
||||
) | rpl::start_with_next([=](int top)
|
||||
{
|
||||
@@ -190,7 +192,7 @@ void Widget::updateAdaptiveLayout() {
|
||||
}
|
||||
|
||||
not_null<PeerData*> Widget::channel() const {
|
||||
return _inner->channel();
|
||||
return _inner->peer();
|
||||
}
|
||||
|
||||
Dialogs::RowDescriptor Widget::activeChat() const {
|
||||
@@ -234,7 +236,7 @@ void Widget::setupShortcuts() {
|
||||
}
|
||||
|
||||
std::shared_ptr<Window::SectionMemento> Widget::createMemento() {
|
||||
auto result = std::make_shared<SectionMemento>(channel(), _item);
|
||||
auto result = std::make_shared<SectionMemento>(channel(), _item, _topicId);
|
||||
saveState(result.get());
|
||||
return result;
|
||||
}
|
||||
@@ -321,4 +323,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,8 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<HistoryItem*> item);
|
||||
HistoryItem *item,
|
||||
ID topicId);
|
||||
|
||||
not_null<PeerData*> channel() const;
|
||||
Dialogs::RowDescriptor activeChat() const override;
|
||||
@@ -77,7 +78,8 @@ private:
|
||||
QPointer<InnerWidget> _inner;
|
||||
object_ptr<FixedBar> _fixedBar;
|
||||
object_ptr<Ui::PlainShadow> _fixedBarShadow;
|
||||
not_null<HistoryItem*> _item;
|
||||
HistoryItem *_item;
|
||||
ID _topicId;
|
||||
|
||||
};
|
||||
|
||||
@@ -86,9 +88,10 @@ class SectionMemento : public Window::SectionMemento
|
||||
public:
|
||||
using Element = HistoryView::Element;
|
||||
|
||||
SectionMemento(not_null<PeerData*> peer, not_null<HistoryItem*> item)
|
||||
SectionMemento(not_null<PeerData*> peer, HistoryItem *item, ID topicId)
|
||||
: _peer(peer),
|
||||
_item(item) {
|
||||
_item(item),
|
||||
_topicId(topicId) {
|
||||
}
|
||||
|
||||
object_ptr<Window::SectionWidget> createWidget(
|
||||
@@ -115,7 +118,7 @@ public:
|
||||
bool upLoaded,
|
||||
bool downLoaded) {
|
||||
_items = std::move(items);
|
||||
_eventIds = std::move(eventIds);
|
||||
_messageIds = std::move(eventIds);
|
||||
_upLoaded = upLoaded;
|
||||
_downLoaded = downLoaded;
|
||||
}
|
||||
@@ -124,8 +127,8 @@ public:
|
||||
return std::move(_items);
|
||||
}
|
||||
|
||||
std::set<uint64> takeEventIds() {
|
||||
return std::move(_eventIds);
|
||||
std::set<uint64> takeMessageIds() {
|
||||
return std::move(_messageIds);
|
||||
}
|
||||
|
||||
bool upLoaded() const {
|
||||
@@ -138,14 +141,13 @@ public:
|
||||
|
||||
private:
|
||||
not_null<PeerData*> _peer;
|
||||
not_null<HistoryItem*> _item;
|
||||
HistoryItem *_item;
|
||||
ID _topicId;
|
||||
int _scrollTop = 0;
|
||||
std::vector<not_null<UserData*>> _admins;
|
||||
std::vector<not_null<UserData*>> _adminsCanEdit;
|
||||
std::vector<OwnedItem> _items;
|
||||
std::set<uint64> _eventIds;
|
||||
std::set<uint64> _messageIds;
|
||||
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
|
||||
@@ -5,6 +5,7 @@
|
||||
//
|
||||
// Copyright @Radolyn, 2024
|
||||
#include "settings_ayu.h"
|
||||
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/ui/boxes/edit_deleted_mark.h"
|
||||
#include "ayu/ui/boxes/edit_edited_mark.h"
|
||||
@@ -17,6 +18,7 @@
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_ayu_styles.h"
|
||||
#include "styles/style_basic.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -346,14 +348,14 @@ void AddCollapsibleToggle(not_null<Ui::VerticalLayout*> container,
|
||||
}
|
||||
|
||||
void AddChooseButtonWithIconAndRightTextInner(not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller,
|
||||
int initialState,
|
||||
std::vector<QString> options,
|
||||
rpl::producer<QString> text,
|
||||
rpl::producer<QString> boxTitle,
|
||||
const style::SettingsButton & st,
|
||||
Settings::IconDescriptor && descriptor,
|
||||
const Fn<void(int)> &setter) {
|
||||
not_null<Window::SessionController*> controller,
|
||||
int initialState,
|
||||
std::vector<QString> options,
|
||||
rpl::producer<QString> text,
|
||||
rpl::producer<QString> boxTitle,
|
||||
const style::SettingsButton &st,
|
||||
Settings::IconDescriptor &&descriptor,
|
||||
const Fn<void(int)> &setter) {
|
||||
auto reactiveVal = container->lifetime().make_state<rpl::variable<int>>(initialState);
|
||||
|
||||
rpl::producer<QString> rightTextReactive = reactiveVal->value() | rpl::map(
|
||||
@@ -505,7 +507,8 @@ void SetupGhostEssentials(not_null<Ui::VerticalLayout*> container) {
|
||||
SetupGhostModeToggle(container);
|
||||
|
||||
auto markReadAfterActionVal = container->lifetime().make_state<rpl::variable<bool>>(settings->markReadAfterAction);
|
||||
auto useScheduledMessagesVal = container->lifetime().make_state<rpl::variable<bool>>(settings->useScheduledMessages);
|
||||
auto useScheduledMessagesVal = container->lifetime().make_state<rpl::variable<
|
||||
bool>>(settings->useScheduledMessages);
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
@@ -559,6 +562,28 @@ void SetupGhostEssentials(not_null<Ui::VerticalLayout*> container) {
|
||||
container->lifetime());
|
||||
AddSkip(container);
|
||||
AddDividerText(container, tr::ayu_UseScheduledMessagesDescription());
|
||||
|
||||
AddSkip(container);
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_SendWithoutSoundByDefault(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->sendWithoutSound)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->sendWithoutSound);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_sendWithoutSound(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
AddSkip(container);
|
||||
AddDividerText(container, tr::ayu_SendWithoutSoundByDefaultDescription());
|
||||
}
|
||||
|
||||
void SetupSpyEssentials(not_null<Ui::VerticalLayout*> container) {
|
||||
@@ -603,6 +628,29 @@ void SetupSpyEssentials(not_null<Ui::VerticalLayout*> container) {
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageSavingSaveForBots(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->saveForBots)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->saveForBots);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_saveForBots(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
}
|
||||
|
||||
void SetupMessageFilters(not_null<Ui::VerticalLayout*> container) {
|
||||
@@ -863,6 +911,166 @@ void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
|
||||
AddDividerText(container, tr::ayu_SettingsContextMenuDescription());
|
||||
}
|
||||
|
||||
void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::ayu_MessageFieldElementsHeader());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageFieldElementAttach(),
|
||||
st::settingsButton,
|
||||
{&st::messageFieldAttachIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showAttachButtonInMessageField)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showAttachButtonInMessageField);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_showAttachButtonInMessageField(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageFieldElementCommands(),
|
||||
st::settingsButton,
|
||||
{&st::messageFieldCommandsIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showCommandsButtonInMessageField)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showCommandsButtonInMessageField);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_showCommandsButtonInMessageField(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageFieldElementTTL(),
|
||||
st::settingsButton,
|
||||
{&st::messageFieldTTLIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showAutoDeleteButtonInMessageField)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showAutoDeleteButtonInMessageField);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_showAutoDeleteButtonInMessageField(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageFieldElementEmoji(),
|
||||
st::settingsButton,
|
||||
{&st::messageFieldEmojiIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showEmojiButtonInMessageField)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showEmojiButtonInMessageField);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_showEmojiButtonInMessageField(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageFieldElementVoice(),
|
||||
st::settingsButton,
|
||||
{&st::messageFieldVoiceIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showMicrophoneButtonInMessageField)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showMicrophoneButtonInMessageField);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_showMicrophoneButtonInMessageField(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
}
|
||||
|
||||
void SetupMessageFieldPopups(not_null<Ui::VerticalLayout*> container) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::ayu_MessageFieldPopupsHeader());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageFieldElementAttach(),
|
||||
st::settingsButton,
|
||||
{&st::messageFieldAttachIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showAttachPopup)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showAttachPopup);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_showAttachPopup(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_MessageFieldElementEmoji(),
|
||||
st::settingsButton,
|
||||
{&st::messageFieldEmojiIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showEmojiPopup)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showEmojiPopup);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_showEmojiPopup(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
}
|
||||
|
||||
void SetupDrawerElements(not_null<Ui::VerticalLayout*> container) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
@@ -1178,8 +1386,6 @@ void SetupSendConfirmations(not_null<Ui::VerticalLayout*> container) {
|
||||
}
|
||||
|
||||
void SetupMarks(not_null<Ui::VerticalLayout*> container) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AddButtonWithLabel(
|
||||
container,
|
||||
tr::ayu_DeletedMarkText(),
|
||||
@@ -1230,9 +1436,9 @@ void SetupFolderSettings(not_null<Ui::VerticalLayout*> container, not_null<Windo
|
||||
// not about folders, but it's a good place for it
|
||||
#ifdef Q_OS_WIN
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_HideNotificationBadge(),
|
||||
st::settingsButtonNoIcon
|
||||
container,
|
||||
tr::ayu_HideNotificationBadge(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->hideNotificationBadge)
|
||||
)->toggledValue(
|
||||
@@ -1420,6 +1626,8 @@ void SetupCustomization(not_null<Ui::VerticalLayout*> container,
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
SetupContextMenuElements(container, controller);
|
||||
SetupMessageFieldElements(container);
|
||||
SetupMessageFieldPopups(container);
|
||||
SetupDrawerElements(container);
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
|
||||
@@ -12,6 +12,33 @@
|
||||
|
||||
namespace AyuMapper {
|
||||
|
||||
constexpr auto kMessageFlagUnread = 0x00000001;
|
||||
constexpr auto kMessageFlagOut = 0x00000002;
|
||||
constexpr auto kMessageFlagForwarded = 0x00000004;
|
||||
constexpr auto kMessageFlagReply = 0x00000008;
|
||||
constexpr auto kMessageFlagMention = 0x00000010;
|
||||
constexpr auto kMessageFlagContentUnread = 0x00000020;
|
||||
constexpr auto kMessageFlagHasMarkup = 0x00000040;
|
||||
constexpr auto kMessageFlagHasEntities = 0x00000080;
|
||||
constexpr auto kMessageFlagHasFromId = 0x00000100;
|
||||
constexpr auto kMessageFlagHasMedia = 0x00000200;
|
||||
constexpr auto kMessageFlagHasViews = 0x00000400;
|
||||
constexpr auto kMessageFlagHasBotId = 0x00000800;
|
||||
constexpr auto kMessageFlagIsSilent = 0x00001000;
|
||||
constexpr auto kMessageFlagIsPost = 0x00004000;
|
||||
constexpr auto kMessageFlagEdited = 0x00008000;
|
||||
constexpr auto kMessageFlagHasPostAuthor = 0x00010000;
|
||||
constexpr auto kMessageFlagIsGrouped = 0x00020000;
|
||||
constexpr auto kMessageFlagFromScheduled = 0x00040000;
|
||||
constexpr auto kMessageFlagHasReactions = 0x00100000;
|
||||
constexpr auto kMessageFlagHideEdit = 0x00200000;
|
||||
constexpr auto kMessageFlagRestricted = 0x00400000;
|
||||
constexpr auto kMessageFlagHasReplies = 0x00800000;
|
||||
constexpr auto kMessageFlagIsPinned = 0x01000000;
|
||||
constexpr auto kMessageFlagHasTTL = 0x02000000;
|
||||
constexpr auto kMessageFlagInvertMedia = 0x08000000;
|
||||
constexpr auto kMessageFlagHasSavedPeer = 0x10000000;
|
||||
|
||||
std::pair<std::string, std::vector<char>> serializeTextWithEntities(not_null<HistoryItem*> item) {
|
||||
if (item->emptyText()) {
|
||||
return std::make_pair("", std::vector<char>());
|
||||
@@ -27,64 +54,112 @@ int mapItemFlagsToMTPFlags(not_null<HistoryItem*> item) {
|
||||
int flags = 0;
|
||||
|
||||
const auto thread = item->topic()
|
||||
? (Data::Thread*) item->topic()
|
||||
? reinterpret_cast<Data::Thread*>(item->topic())
|
||||
: item->history();
|
||||
const auto unseen = item->unread(thread);
|
||||
if (unseen) {
|
||||
flags |= 1;
|
||||
if (item->unread(thread)) {
|
||||
flags |= kMessageFlagUnread;
|
||||
}
|
||||
|
||||
if (item->out()) {
|
||||
flags |= 2;
|
||||
flags |= kMessageFlagOut;
|
||||
}
|
||||
|
||||
if (const auto reply = item->Get<HistoryMessageForwarded>()) {
|
||||
flags |= 4;
|
||||
if (item->Get<HistoryMessageForwarded>()) {
|
||||
flags |= kMessageFlagForwarded;
|
||||
}
|
||||
|
||||
if (const auto reply = item->Get<HistoryMessageReply>()) {
|
||||
flags |= 8;
|
||||
if (item->Get<HistoryMessageReply>()) {
|
||||
flags |= kMessageFlagReply;
|
||||
}
|
||||
|
||||
if (item->mentionsMe()) {
|
||||
flags |= 16;
|
||||
flags |= kMessageFlagMention;
|
||||
}
|
||||
|
||||
if (item->isUnreadMedia()) {
|
||||
flags |= 32;
|
||||
flags |= kMessageFlagContentUnread;
|
||||
}
|
||||
|
||||
if (item->definesReplyKeyboard()) {
|
||||
flags |= kMessageFlagHasMarkup;
|
||||
}
|
||||
|
||||
if (!item->originalText().entities.empty()) {
|
||||
flags |= kMessageFlagHasEntities;
|
||||
}
|
||||
|
||||
if (item->displayFrom()) {
|
||||
// todo: maybe wrong
|
||||
flags |= kMessageFlagHasFromId;
|
||||
}
|
||||
|
||||
if (item->media()) {
|
||||
flags |= kMessageFlagHasMedia;
|
||||
}
|
||||
|
||||
if (item->hasViews()) {
|
||||
flags |= kMessageFlagHasViews;
|
||||
}
|
||||
|
||||
if (item->viaBot()) {
|
||||
flags |= kMessageFlagHasBotId;
|
||||
}
|
||||
|
||||
if (item->isSilent()) {
|
||||
flags |= 8192;
|
||||
flags |= kMessageFlagIsSilent;
|
||||
}
|
||||
|
||||
if (item->isPost()) {
|
||||
flags |= 16384;
|
||||
flags |= kMessageFlagIsPost;
|
||||
}
|
||||
|
||||
if (item->Get<HistoryMessageEdited>()) {
|
||||
flags |= kMessageFlagEdited;
|
||||
}
|
||||
|
||||
if (item->Get<HistoryMessageSigned>()) {
|
||||
flags |= kMessageFlagHasPostAuthor;
|
||||
}
|
||||
|
||||
if (item->groupId()) {
|
||||
flags |= kMessageFlagIsGrouped;
|
||||
}
|
||||
|
||||
if (item->isScheduled()) {
|
||||
flags |= 262144;
|
||||
flags |= kMessageFlagFromScheduled;
|
||||
}
|
||||
|
||||
// todo: legacy
|
||||
// if (item->isLegacy()) {
|
||||
// flags |= 524288;
|
||||
// }
|
||||
if (!item->reactions().empty()) {
|
||||
flags |= kMessageFlagHasReactions;
|
||||
}
|
||||
|
||||
if (item->hideEditedBadge()) {
|
||||
flags |= 2097152;
|
||||
flags |= kMessageFlagHideEdit;
|
||||
}
|
||||
|
||||
if (item->hasPossibleRestrictions()) {
|
||||
flags |= kMessageFlagRestricted;
|
||||
}
|
||||
|
||||
if (item->repliesCount() > 0) {
|
||||
flags |= kMessageFlagHasReplies;
|
||||
}
|
||||
|
||||
if (item->isPinned()) {
|
||||
flags |= 16777216;
|
||||
flags |= kMessageFlagIsPinned;
|
||||
}
|
||||
|
||||
if (item->forbidsForward()) {
|
||||
flags |= 67108864;
|
||||
if (item->ttlDestroyAt() > 0) {
|
||||
flags |= kMessageFlagHasTTL;
|
||||
}
|
||||
|
||||
if (item->topic() && item->topicRootId() == item->id) {
|
||||
flags |= 134217728;
|
||||
if (item->invertMedia()) {
|
||||
flags |= kMessageFlagInvertMedia;
|
||||
}
|
||||
|
||||
if (item->savedFromSender()) {
|
||||
// todo: maybe wrong
|
||||
flags |= kMessageFlagHasSavedPeer;
|
||||
}
|
||||
|
||||
return flags;
|
||||
|
||||