Compare commits

...

401 commits
v5.14.3 ... dev

Author SHA1 Message Date
bleizix
1dfe68e9f3 fix: cache icons in settings 2025-07-29 01:55:47 +03:00
bleizix
64b824671a fix: don't increment channel views 2025-07-29 01:55:46 +03:00
bleizix
4940d85b29 fix: AyuForward crashes & improvements 2025-07-29 01:55:43 +03:00
AlexeyZavar
157039946f fix: allow copying inline button links 2025-07-16 00:33:27 +03:00
AlexeyZavar
09083beed0 fix: entities serialization 2025-07-16 00:00:55 +03:00
AlexeyZavar
8aa6c4582c fix: rename hide reactions
The logic is inverted everywhere, e.g. when `hideGroupReactions` is true, it's actually meant to show, not hide
2025-07-15 23:40:16 +03:00
AlexeyZavar
aeb610ff70 feat: copy pack ID & copy single emoji ID 2025-07-15 23:29:37 +03:00
AlexeyZavar
1586386d6c fix: hide panel if reactions are hidden 2025-07-15 22:48:47 +03:00
AlexeyZavar
2eee51f891 fix: tray dot misalignment 2025-07-15 22:07:14 +03:00
AlexeyZavar
adf46969ca chore: update nlohmann json 2025-07-15 21:33:19 +03:00
AlexeyZavar
bd6ee188f8 chore: update sqlite3 2025-07-15 21:20:05 +03:00
AlexeyZavar
57893bdbf8 chore: update devs list 2025-07-15 21:17:14 +03:00
AlexeyZavar
7a37ab1b25 fix: message seconds on linux 2025-07-15 21:06:09 +03:00
AlexeyZavar
72618f98d1 fix: message field configuration in forums 2025-07-15 20:26:29 +03:00
AlexeyZavar
d98e6fd51e fix: disable video ads 2025-07-15 19:48:19 +03:00
bleizix
608bcf7d42 feat: save deleted entities 2025-07-15 17:47:25 +03:00
bleizix
06d8d14ad7 fix: AyuForward crashes 2025-07-15 17:40:17 +03:00
AlexeyZavar
aef400db0f fix: message shot layout 2025-07-15 17:27:28 +03:00
AlexeyZavar
cdfdaf9f5f fix: make it build 2025-07-15 17:22:19 +03:00
AlexeyZavar
5270f155ff Merge tag 'v5.16.4' into dev 2025-07-15 16:48:06 +03:00
John Preston
0514f13af0 Version 5.16.4.
- Fix problem with negative unread counters.
- Fix stars values display in statistics.
- Fix crash in messages fee disabling.
2025-07-14 20:51:13 +04:00
John Preston
e6a6763228 Fix build with GCC. 2025-07-14 20:51:13 +04:00
23rd
38c74bf2cf Added lottie icon and button to dialogs widget when dialogs are empty. 2025-07-14 19:42:17 +04:00
John Preston
a770e47575 Fix build with Xcode. 2025-07-14 19:07:05 +04:00
John Preston
c998352ab7 Possibly fix a crash in subsection tabs.
Fixes #29550.
2025-07-14 18:30:59 +04:00
John Preston
3683fa3814 Remove paid message service info in groups. 2025-07-14 17:40:30 +04:00
John Preston
e62881e08b Fix blocking users in channel direct messages.
Fixes #29549.
2025-07-14 17:15:25 +04:00
John Preston
01e313e56b Fix crash after deleting sublist.
Fixes #29548.
2025-07-14 15:40:14 +04:00
John Preston
2dd5f80468 Allow transferring gifts to channels. 2025-07-14 14:29:31 +04:00
John Preston
275fb3e96a Fix actions for converted gifts. 2025-07-14 14:24:41 +04:00
John Preston
3463916b9b De-duplicate icon for checklist task adding. 2025-07-14 14:24:08 +04:00
John Preston
a20de2515a Fix text-send permissions in groups.
Fixes #29546.
2025-07-14 13:58:38 +04:00
John Preston
79ea992a0f Suggest relevant min price for gift resale. 2025-07-14 13:28:57 +04:00
John Preston
0132436dc8 Move the NEW badge to gifts (market). 2025-07-14 11:29:57 +04:00
John Preston
a285c1abec Fix unsupported filtering in monoforum export.
Fixes #29545.
2025-07-14 11:21:14 +04:00
John Preston
1c41e01f0d Update API scheme to layer 209. 2025-07-14 09:54:04 +04:00
John Preston
f2e53ea490 Try scrolling to the task on jump. 2025-07-11 19:26:01 +04:00
John Preston
bff86b90fb Highlight tasks from reply/service messages. 2025-07-11 19:26:01 +04:00
John Preston
b5c9b6f552 Make and display replies to tasks. 2025-07-11 19:26:01 +04:00
John Preston
23f5102f1b Update API scheme to layer 208. 2025-07-11 19:26:01 +04:00
John Preston
bf51e911b8 Fix unread counters with monoforums.
Fixes #29544.
2025-07-11 19:26:00 +04:00
John Preston
4039d7ab71 Update tgcalls. 2025-07-11 19:26:00 +04:00
John Preston
77a09a0e59 Fix some search options. 2025-07-11 19:26:00 +04:00
John Preston
154c777788 Improve formatting for some strange cases. 2025-07-11 19:26:00 +04:00
John Preston
628c36c87d Fix crash in the private fee disable. 2025-07-11 19:26:00 +04:00
John Preston
a746b7abcf Fix reactions in media-only service messages.
Fixes #29543.
2025-07-11 19:26:00 +04:00
John Preston
64184e6c90 Support controls on sponsored in video. 2025-07-11 19:26:00 +04:00
John Preston
ecc955d2ce Show sponsored messages in video. 2025-07-11 19:26:00 +04:00
John Preston
f7e1b2c70c Fix build with QT_VERSION_MAJOR provided. 2025-07-11 19:26:00 +04:00
John Preston
284cbda7c0 Track shown sponsored in video. 2025-07-11 19:26:00 +04:00
John Preston
e5ca9e4c39 Ability to request video ads. 2025-07-11 19:26:00 +04:00
John Preston
02aaa71e78 Fix updating sublist chats in Saved Messages.
Fixes #29448.
2025-07-11 19:26:00 +04:00
AlexeyZavar
f56b5ea03e fix: forward box crash
Co-authored-by: mmlo <eu@memelo.dev>
2025-07-10 14:43:33 +03:00
John Preston
6afd4dcdd1 Fix stats values display. 2025-07-09 09:33:50 +04:00
23rd
52bb189996 Fixed possible crash when paste invalid proxy link to proxy box. 2025-07-09 09:33:50 +04:00
AlexeyZavar
aafdac6da4 fix: localization variables loading
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
2025-07-09 01:20:57 +03:00
AlexeyZavar
bd202fce21 fix: make it build 2025-07-09 00:03:21 +03:00
AlexeyZavar
f450001edb Merge tag 'v5.16.3' into dev 2025-07-09 00:02:08 +03:00
John Preston
8ff6f9af45 Remove unused DELAYLOAD. 2025-07-08 23:24:48 +04:00
John Preston
36bb1d0cf3 Version 5.16.3.
- Allow removing / charging fee in channel direct messages.
- Don't offer creating checklists in channels.
- Support author channel in gifts.
2025-07-08 22:44:40 +04:00
John Preston
c7c8e39e20 Fix build with Xcode. 2025-07-08 22:44:40 +04:00
John Preston
8e9630459b Add session_id to mtproto logs. 2025-07-08 21:32:49 +04:00
Ilya Fedin
1c53fca925 Remove unused yasm dependency from snap 2025-07-08 20:38:51 +04:00
Ilya Fedin
7d78de0673 Get rid of QT_GENERATE_SBOM=OFF 2025-07-08 20:38:51 +04:00
John Preston
f8acc55365 Remove checklist creation in broadcasts. 2025-07-08 20:29:44 +04:00
John Preston
c3157fe90d Fix incorrect dc for stars revenue requests. 2025-07-08 20:29:43 +04:00
John Preston
35b129287b Support RemoveFee/ChargeFee in monoforums. 2025-07-08 20:29:43 +04:00
John Preston
d05155a403 Show who released the gift. 2025-07-08 20:29:43 +04:00
AlexeyZavar
e5ca216c99 fix: make it build
Some checks are pending
Docker. / Ubuntu (push) Waiting to run
2025-07-07 02:56:10 +03:00
AlexeyZavar
13a96ee1d3 Merge tag 'v5.16.2' into dev 2025-07-07 01:23:33 +03:00
AlexeyZavar
12879207ef fix: restrict saving content toggle 2025-07-07 00:59:02 +03:00
bleizix
e097a5de58
feat: implement AyuForward 2025-07-06 23:54:45 +03:00
Neurotoxin001
0cd2617576
feat: delete own messages from groups 2025-07-06 21:07:32 +03:00
AlexeyZavar
6c303b8f87 chore: update README 2025-07-06 18:55:27 +03:00
AlexeyZavar
91224d8a1c fix: use tgdatabase bot again 2025-07-06 18:50:10 +03:00
AlexeyZavar
48e961392d fix: allow saving paid media 2025-07-06 18:09:54 +03:00
AlexeyZavar
5b08a1adfb chore: update langs 2025-07-06 17:37:35 +03:00
Neurotoxin001
3bbb9daa34
feat: disable reactions in channels and groups 2025-07-06 17:22:26 +03:00
AlexeyZavar
3f5f17705d feat: select multiple chats by holding ctrl or shift in forward box 2025-07-06 11:01:34 +03:00
John Preston
f0aca45b11 Update API scheme on layer 207. 2025-07-04 21:31:20 +04:00
John Preston
d205e3b4a3 Version 5.16.2.
- Fix crash in some checklists.
- Fix problems with some private chats message sending.
- Fix chats list preview for rejected/balance-low suggestions.
2025-07-04 20:23:44 +04:00
John Preston
8b40a77297 Fix build. 2025-07-04 20:23:37 +04:00
John Preston
a7e87cc722 Try reducing self userFull requests. 2025-07-04 17:57:37 +04:00
John Preston
09f106bd49 Remove .00 from credits amount. 2025-07-04 17:57:35 +04:00
John Preston
8a0869fb75 Fix phrases for rejected/failed suggestion. 2025-07-04 17:56:35 +04:00
John Preston
7ccf26310d Fix spoilers in todo lists. 2025-07-04 17:51:02 +04:00
23rd
e1420ba26c Decreased size of ministars for buttons in media for history view. 2025-07-04 09:46:09 +03:00
23rd
c693c9cd44 Fixed history list of earn channel for currency. 2025-07-03 22:13:38 +03:00
GitHub Action
e698557092 Update User-Agent for DNS to Chrome 137.0.0.0. 2025-07-03 17:46:26 +04:00
John Preston
1238c90450 Fix crash in Todo Lists. 2025-07-03 13:52:54 +04:00
John Preston
e991dca5a0 Version 5.16.1.
- Fix inline keyboard updating in bot messages.
- Fix possible crash in fast chat switching.
- Different minor fixes.
2025-07-02 17:32:50 +04:00
23rd
37093c9f33 Slightly improved mini stars for button in history view premium gifts. 2025-07-02 17:32:50 +04:00
John Preston
1dd4f62ece Fix editing direct message prices. 2025-07-02 17:32:50 +04:00
Ilya Fedin
9ec27aad53 Update patches 2025-07-02 17:24:38 +04:00
Ilya Fedin
f1d3a946d5 Get rid of libtgvoip leftovers 2025-07-02 17:24:38 +04:00
John Preston
fd24f7045e Fix editing price per message. 2025-07-02 12:36:07 +04:00
John Preston
33671e7737 Save correct starsPerMessage for admins. 2025-07-02 12:04:29 +04:00
John Preston
13ecc6a56b Use separate phrase for Update Time. 2025-07-02 12:04:01 +04:00
23rd
20815eecbc Fixed request of recent gifts only when peer has gifts. 2025-07-02 10:18:22 +03:00
23rd
f2f5ffd861 Moved out Type of ministars from class to separated enum class. 2025-07-02 10:18:22 +03:00
John Preston
b0971601b1 Add distinct item for Poll in menu. 2025-07-02 11:15:48 +04:00
23rd
7253d764d2 Guarded requests of recent gifts for button from shared media section. 2025-07-02 11:05:48 +04:00
23rd
71f3a1f7cf Fixed wrong duplicated gifts in button from shared media section. 2025-07-02 11:05:41 +04:00
John Preston
950659b29d Fix aggregate checklists updates. 2025-07-02 10:57:20 +04:00
John Preston
b4392d0e3c Update cmake submodule. 2025-07-02 10:27:14 +04:00
John Preston
4571302642 Fix usd values in stats. 2025-07-02 10:08:12 +04:00
Ilya Fedin
5121f04d66 Remove jemalloc 2025-07-02 09:38:24 +04:00
Ilya Fedin
77642d2876 Limit the amount of malloc arenas and call malloc_trim periodically with glibc
To prevent excessive memory usage looking like memory leak
2025-07-02 09:38:24 +04:00
John Preston
ef69796798 Fix bot buttons refresh. 2025-07-02 07:20:51 +04:00
John Preston
869854fbc2 Version 5.16: Fix build with GCC. 2025-07-01 23:53:52 +04:00
John Preston
4584d912cc Version 5.16.
- Create private and group checklists.
- Suggest Posts in Channels.
- Monetizing via Suggested Posts.
2025-07-01 23:05:11 +04:00
John Preston
ddd3d38dac Use more separate phrases. 2025-07-01 23:00:40 +04:00
John Preston
fcba6bc7d7 Fix first days of some months. 2025-07-01 22:20:09 +04:00
John Preston
a147c0fb4f Update API scheme on layer 206. 2025-07-01 22:10:30 +04:00
John Preston
c3cc3fa9bd Separate phrase for paid messages placeholder. 2025-07-01 22:10:14 +04:00
23rd
0f0e14132a Added ability to copy and paste proxy list with shortcuts. 2025-07-01 19:33:25 +04:00
23rd
a2c2ed0b4a Added ability to clear proxy list from top context menu in settings. 2025-07-01 19:33:25 +04:00
23rd
8bebca4502 Added ability to share your proxy list with clipboard. 2025-07-01 19:33:25 +04:00
23rd
e9a550d458 Added ability to add proxies list from clipboard. 2025-07-01 19:33:25 +04:00
23rd
bee567f2b1 Added transaction id info to channel earn history entries. 2025-07-01 19:33:25 +04:00
23rd
0fd7061671 Replaced Data::EarnHistoryEntry with Data::CreditsHistoryEntry. 2025-07-01 19:33:25 +04:00
23rd
96951576c1 Fixed credits display in statistic charts. 2025-07-01 19:33:24 +04:00
John Preston
a665d7cc3c Show USD estimate in suggest posts. 2025-07-01 19:32:49 +04:00
John Preston
9b989329d4 Fix muted state of monoforums. 2025-07-01 15:59:20 +04:00
John Preston
eccfd75a83 Scroll to active tab in subsection tabs. 2025-07-01 15:10:21 +04:00
John Preston
7e8a152eef Fix crash in topic links opening. 2025-07-01 14:39:28 +04:00
John Preston
44810f95a5 Fix clearing history in legacy groups. 2025-07-01 14:12:35 +04:00
John Preston
bcd42dbb6a Block forwarded todo lists. 2025-07-01 13:01:29 +04:00
John Preston
6e77126a65 Fix task completion date tooltip. 2025-07-01 12:29:00 +04:00
John Preston
735bbef655 Fix phone/card click handler generation. 2025-07-01 12:23:55 +04:00
John Preston
7006a07dd3 Show completed-by names in tasks. 2025-07-01 12:10:08 +04:00
John Preston
7284926db4 Support links in tasks. 2025-07-01 11:26:56 +04:00
John Preston
b506bf9506 Fix webpage preview glitch in small bubbles. 2025-07-01 11:05:02 +04:00
John Preston
3fea28f8b0 Warn admins about commission and stars. 2025-06-30 22:23:55 +04:00
John Preston
cc6649667c Revert "Get rid of /DELAYLOAD"
This reverts commit 6586791655.
2025-06-30 18:29:28 +04:00
John Preston
24121fbbce Improve conditions / phrases. 2025-06-30 18:26:53 +04:00
John Preston
ef280dae3e Open ready-preloaded TON section. 2025-06-30 16:56:42 +04:00
John Preston
49f580a36b Use better file origin for gift stickers. 2025-06-30 13:27:13 +04:00
John Preston
a56d2b1313 Better handle min in linked channels. 2025-06-30 13:22:04 +04:00
John Preston
f083180401 Support correct sitckers for TON gifts. 2025-06-30 12:32:33 +04:00
23rd
7b870edefa Added specific color for blue currency icon. 2025-06-30 11:22:08 +03:00
23rd
a293fa1de6 Replaced std::map with flat_map for selected items in history list. 2025-06-30 10:55:25 +03:00
23rd
3a8898dd24 Added ability to share proxy with QR code. 2025-06-30 10:55:25 +03:00
23rd
b97c8b9a17 Fixed order of saving grouped media from bulk download menu item. 2025-06-30 10:55:25 +03:00
23rd
a0a8d76441 Proceeded returned earn history entries as inbound. 2025-06-30 09:51:53 +03:00
23rd
9199612cf7 Slightly improved macOS build docs by adding storage requirements. 2025-06-30 01:01:15 +03:00
23rd
abfd37c045 Added suffix to version string for debug builds. 2025-06-29 23:40:24 +03:00
23rd
ac243136b9 Replaced AboutBox with generic box. 2025-06-29 23:40:24 +03:00
23rd
c93e948bb3 Added support of maximum amount of credits to withdraw from app config. 2025-06-29 22:34:46 +03:00
23rd
388803ffdb Fit transactions id from credits history entries to table cell. 2025-06-29 22:34:46 +03:00
23rd
a809ccf229 Added support of special sticker pack for currency history entries. 2025-06-29 22:34:46 +03:00
23rd
d1796a515e Fixed comparing of credits amount with different types. 2025-06-29 22:34:46 +03:00
23rd
a11d3efed6 Added support of usd rate for withdrawal from app config. 2025-06-29 22:34:46 +03:00
Neurotoxin001
a8fb7cd225
fix: hide messages group 2025-06-29 22:28:41 +03:00
23rd
9d7aab4326 Added initial support of personal earn currency history. 2025-06-29 21:14:20 +03:00
23rd
a3b76fed4c Added initial implementation of section for personal currency settings. 2025-06-29 21:14:20 +03:00
23rd
21f5f96d69 Added entry point to section of personal currency settings. 2025-06-29 21:14:20 +03:00
23rd
70d3061d59 Added support of lottie icon to premium top bar. 2025-06-29 21:14:20 +03:00
23rd
8f7a66a21a Improved spell checking on macOS. 2025-06-29 21:14:20 +03:00
23rd
cd8e944751 Added recent gifts to section of shared media from info profile. 2025-06-29 21:14:20 +03:00
23rd
66e05594f6 Fixed display of top bar suggestion on premium value changed. 2025-06-29 21:14:20 +03:00
23rd
d06a9e2e5c Added missing shortcut to toggle fullscreen for windows on macOS. 2025-06-29 21:14:20 +03:00
23rd
64df9222dd Added handle of flood errors on requesting to check chat invite.
Fixed #29368.
2025-06-29 21:14:20 +03:00
23rd
900ac46583 Moved out gifts from userpic to description in credits history list. 2025-06-29 21:14:20 +03:00
23rd
fda1c8399f Added icon to big button in credits settings. 2025-06-29 21:14:20 +03:00
23rd
c2cd4fc75f Improved style of credits settings. 2025-06-29 21:14:20 +03:00
23rd
ef5a9eb777 Added some utils to convenient insert of credits icons to text labels. 2025-06-29 21:14:20 +03:00
23rd
8e3100acdd Added ability to provide max width to about label in premium top bar. 2025-06-29 21:14:20 +03:00
23rd
50d74fcf25 Fixed build with layer 206. 2025-06-29 21:14:20 +03:00
John Preston
140862ce5a Update API scheme on layer 206. 2025-06-29 20:42:52 +03:00
John Preston
966453dd8f Update scheme on layer 206. Build broken. 2025-06-27 22:54:34 +04:00
John Preston
fcd37adc38 Fix build with Xcode. 2025-06-27 22:53:37 +04:00
John Preston
6a3989488e Fix possible crash in quick replies. 2025-06-27 21:29:39 +04:00
John Preston
fbfdef8f9a Remove balance for admins changing prices. 2025-06-27 21:29:39 +04:00
John Preston
b66c61573f Make counter-suggestions link originals. 2025-06-27 21:29:39 +04:00
John Preston
9186b28b60 Fix suggest of a forward. 2025-06-27 21:29:39 +04:00
John Preston
915a1b105c Add balance of stars / TON. 2025-06-27 21:29:39 +04:00
John Preston
12b9b3ce71 To-Do List -> Checklist. 2025-06-27 21:29:39 +04:00
John Preston
deb4c48551 Server-side min/max values for TON suggests. 2025-06-27 21:29:39 +04:00
John Preston
9dfaac8582 Optimize some request channel full requests. 2025-06-27 21:29:39 +04:00
John Preston
bd5b9f5347 Make less channel full requests. 2025-06-27 21:29:39 +04:00
John Preston
ff4b9a3cfe Improve suggest post UI. 2025-06-27 21:29:35 +04:00
John Preston
e844d7ed89 Use phrase from lang.strings for TON gift. 2025-06-27 21:29:35 +04:00
John Preston
f3ae8f7f75 Fix styles in discussion group management. 2025-06-27 21:29:35 +04:00
John Preston
f137eddbf9 Use correct premium promo for To-Do Lists. 2025-06-27 21:29:35 +04:00
John Preston
c70f75b21a Add Stars/TON checks to suggestions. 2025-06-27 21:29:35 +04:00
John Preston
141fb875f9 Add icons to suggestion buttons. 2025-06-27 21:29:35 +04:00
John Preston
344d47e7dd Fix stars amount check. 2025-06-27 21:29:35 +04:00
John Preston
4840a9094b Check amounts of stars/TON. 2025-06-27 21:29:35 +04:00
John Preston
6f305c8974 Improve layout in suggested price box. 2025-06-27 21:29:35 +04:00
John Preston
c83bae3bb5 Use correct icon in post suggesting. 2025-06-27 21:29:35 +04:00
John Preston
4c1b962486 Support adding an offer to existing message. 2025-06-27 21:29:35 +04:00
John Preston
b929e2a7b2 Update API scheme on layer 160, show correct warnings. 2025-06-27 21:29:35 +04:00
John Preston
1ecd7aa7cf Implement suggestion of changes. 2025-06-27 21:29:34 +04:00
John Preston
881aed50ea Support _suggestOptions for changes in ComposeControls. 2025-06-27 21:29:34 +04:00
John Preston
6272b79f70 Allow suggesting with TON. 2025-06-27 21:29:34 +04:00
John Preston
0fa50f1951 Update API scheme on layer 206. 2025-06-27 21:29:34 +04:00
John Preston
dc19f2e76c Start suggesting changes to messages by editing. 2025-06-27 21:29:34 +04:00
John Preston
498116c3f6 Show correctly change suggestions. 2025-06-27 21:29:34 +04:00
John Preston
e29dcf7489 Update API scheme on layer 206. Re-Suggest. 2025-06-27 21:29:34 +04:00
John Preston
ec28eea7f0 Make keyboard fully shown with media. 2025-06-27 21:29:34 +04:00
John Preston
ebce4d0f31 Show suggested service info. 2025-06-27 21:29:34 +04:00
John Preston
bf9492e083 Show approve/decline service messages. 2025-06-27 21:29:34 +04:00
John Preston
cb987c1baf PoC suggested accept/decline. 2025-06-27 21:29:34 +04:00
John Preston
e4a4be1f53 Don't show visibility status in opened-by-link gifts. 2025-06-27 21:29:34 +04:00
John Preston
f6d1fe6c04 Update API scheme on layer 206. 2025-06-27 21:29:34 +04:00
John Preston
7e5a29a5cc PoC suggesting posts to channels. 2025-06-27 21:29:34 +04:00
John Preston
8dbc175c02 Update API scheme on layer 206. 2025-06-27 21:29:33 +04:00
John Preston
0473374d51 Allow admin sending to monoforum for free. 2025-06-27 21:29:33 +04:00
John Preston
b2d7342b9e Attempt to fix monoforum muted setting. 2025-06-27 21:29:33 +04:00
John Preston
b965aecc6c Update API scheme to layer 206. 2025-06-27 21:29:33 +04:00
John Preston
9290c90bdc Allow fully editing todo lists. 2025-06-27 21:29:33 +04:00
John Preston
d83a80ec53 Improve adding tasks to todo lists. 2025-06-27 21:29:33 +04:00
John Preston
248fe1b53f Add tasks to todo lists. 2025-06-27 21:29:33 +04:00
John Preston
bf217bf7aa Check premium for todo lists actions. 2025-06-27 21:29:33 +04:00
John Preston
e5de8e22b7 Add fireworks on ending a task list. 2025-06-27 21:29:33 +04:00
John Preston
5666e84d92 Add ability to create todo lists. 2025-06-27 21:29:33 +04:00
John Preston
a97d1b8669 Support task lists view/update/actions. 2025-06-27 21:29:33 +04:00
John Preston
06db13a0ab Update API scheme to layer 205. 2025-06-27 21:29:33 +04:00
John Preston
9832af7cce Show messages from channels in monoforums. 2025-06-27 21:29:32 +04:00
Yagiz Nizipli
cf4a617f2b
update ada-url to v3.2.4 (#29353) 2025-06-27 09:56:01 -07:00
Sean Wei
5a6a5fd4d1 Change const T&& parameters to T&& to enable proper move semantics
Previously some constructors/functions used `const T&&`, which prevents
calling the move constructor. This commit removes the `const` qualifier
so that `std::move` actually performs a move.
2025-06-27 20:50:08 +04:00
Ilya Fedin
e6ebc19b4f Switch qt snapcraft part to cmake plugin 2025-06-27 20:49:30 +04:00
Neurotoxin001
2250fe75c4
fix: use custom serialization for settings
The error occurred when adding a new fields, for example showForwards, to the AyuGramSettings class. The NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT macro was used for serialization, which works correctly only when the number of parameters is up to 64. After extending the structure, the macro failed to expand and the compiler started generating messages about undefined identifiers like NLOHMANN_JSON_TO.

The fix was to replace the problematic macro with manual implementation of to_json and from_json functions. For each field, it is now explicitly specified how to serialize and deserialize it, which eliminates the limitation on the number of arguments. Additionally, auxiliary macros NLOHMANN_JSON_TO, NLOHMANN_JSON_FROM and NLOHMANN_JSON_FROM_WITH_DEFAULT have been declared in case they are absent in the header file used.

More info: https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/#notes
2025-06-26 19:08:41 +03:00
AlexeyZavar
e27b1840c6 chore: update README
Co-authored-by: Max Balashov <rsg245@yandex.com>
2025-06-26 18:57:24 +03:00
Neurotoxin001
717d197998
fix: image order when downloading albums 2025-06-26 18:36:25 +03:00
Ilya Fedin
02dd0dbbef Push Docker image to GHCR again 2025-06-24 08:47:28 +04:00
John Preston
2e4a437d32 Version 5.15.4.
- Fix updating messages in Saved Messages subchats.
- Fix possible issues with mouse cursor on Linux.
2025-06-12 22:07:05 +04:00
John Preston
63e1d6dab6 Fix saved messages sublists updates. 2025-06-12 22:07:02 +04:00
Daniel Novomeský
23133499c7 Update dav1d, openh264, libwebp, libavif, libde265, libheif 2025-06-10 08:56:12 +04:00
Ilya Fedin
67bd87b50c Prevent non-Qt harfbuzz/libpng from being linked 2025-06-10 08:55:58 +04:00
Ilya Fedin
6d31a4246f Fix default cursor path 2025-06-09 20:24:09 +04:00
John Preston
d4f38b6d66 Version 5.15.3: Revert cmake_helpers downgrade. 2025-06-09 11:05:47 +04:00
John Preston
959229f143 Version 5.15.3.
- Fix new contact top bar appearance.
- Remove change photo button for channel direct messages.
2025-06-09 09:39:02 +04:00
John Preston
22f9b1a0b1 Hide photo change button for monoforums. 2025-06-09 09:37:51 +04:00
John Preston
a8fc5a722f Fix display of contact status. 2025-06-09 09:37:51 +04:00
AlexeyZavar
69420f5750 chore: refactor & reformat settings 2025-06-08 11:00:04 +03:00
AlexeyZavar
20976ac9f9 fix: ttl messages destroying 2025-06-06 21:27:39 +03:00
AlexeyZavar
dc33accae7 chore: update sqlite3 2025-06-06 11:38:15 +03:00
AlexeyZavar
29d1f1f14a fix: copy sticker owner ID if not found 2025-06-06 11:37:52 +03:00
Ilya Fedin
6102119673 Update cmake_helpers 2025-06-06 11:14:06 +04:00
Ilya Fedin
0c635a05ff Allow overriding jobs count in Dockerfile 2025-06-06 11:14:06 +04:00
Ilya Fedin
307a7791df Set Implib commit 2025-06-06 11:14:06 +04:00
Ilya Fedin
49b056a0ce Update xcb libraries to avoid freedesktop's anongit 2025-06-06 11:14:06 +04:00
Ilya Fedin
af58ffadcb Use cache action for Docker layers cache 2025-06-06 11:13:56 +04:00
John Preston
dcbda7b3af Version 5.15.2.
- Fix sending messages in new forum layout.
- Add statistics for user stars.
2025-06-05 22:55:29 +04:00
John Preston
553cc0c6ae Fix new forum messages sending. 2025-06-05 21:26:50 +04:00
23rd
067dcbfbeb Added initial entry point for self statistics of credits. 2025-06-05 19:59:02 +03:00
23rd
759258bb39 Added support of statistics availability to Credits component. 2025-06-05 19:49:25 +03:00
John Preston
3667ef551c Version 5.15.1.
- Fix launch on Windows 7.
- Fix launch on older Linux distributions.
- Fix crash in group chat message right click.
- Fix unread counters in channel direct messages.
- Don't generate "User joined" message in channel direct messages.
- Fix some other glitches in new forums and channel direct messages.
2025-06-05 19:08:28 +04:00
Ilya Fedin
a3308087a5 Fix static libstdc++ link 2025-06-05 16:57:04 +04:00
John Preston
bfb4652425 Realtime update admin status in members list. 2025-06-05 16:09:41 +04:00
John Preston
03c24e2906 Show better monoforum chat info column. 2025-06-05 15:29:26 +04:00
John Preston
dc61faace1 Handle disabling direct messages in channel. 2025-06-05 15:29:26 +04:00
John Preston
73ea86ceeb Improve monoforum chat profiles. 2025-06-05 14:24:30 +04:00
John Preston
9a622ab466 Add view channel button to monoforum info. 2025-06-05 14:09:03 +04:00
John Preston
65cfd6c81c Fix new forum layout search and topics list. 2025-06-05 13:55:55 +04:00
John Preston
08681ac1b9 Show join requests in new forums layout. 2025-06-05 13:39:33 +04:00
John Preston
f9acb5d19b Fix activation of wrong tab after new window. 2025-06-05 13:00:14 +04:00
John Preston
e9e187c58b Ctrl+Click to open subsection in a new window. 2025-06-05 12:54:59 +04:00
John Preston
3bc20c3550 Save last opened subsection within a launch. 2025-06-05 12:47:16 +04:00
John Preston
4b25406d14 Remove delay when switching subsection tabs. 2025-06-05 12:19:27 +04:00
John Preston
265b7904a8 Fix blockquote/code captions to media in reply bar. 2025-06-05 12:10:31 +04:00
John Preston
fb2274c58d Fix glitch in new forum layout opening. 2025-06-05 12:04:28 +04:00
John Preston
3cfdc9d897 Fix setting group emoji status. 2025-06-05 11:40:05 +04:00
John Preston
11986ac698 Show star in channel direct messages settings. 2025-06-05 11:33:43 +04:00
John Preston
a08436ecd2 Fix unread counters in filters with monoforums. 2025-06-05 11:04:23 +04:00
John Preston
e92adf94a7 Improve adaptive loading in subsection tabs. 2025-06-05 10:45:13 +04:00
Ilya Fedin
d25356917d Stop setting CMAKE_EXE_LINKER_FLAGS in actions 2025-06-05 09:56:19 +04:00
John Preston
0adb3b062f Use only first name in birthday notification. 2025-06-05 09:55:16 +04:00
John Preston
9316480884 Don't generate joined message in monoforums. 2025-06-05 09:22:44 +04:00
John Preston
4e5082f6c6 Fix comments root view position.
Fixes #29389.
2025-06-05 09:06:19 +04:00
John Preston
16d5dbe71c Fix crash in group chat context menu.
Fixes #29387.
2025-06-05 08:59:07 +04:00
John Preston
133d7874e3 Revert d3d compiler to 10.0.22621.3233. 2025-06-05 08:56:53 +04:00
John Preston
4659d5db5d Version 5.15.
- Send Direct Messages to Channels.
- Enable New Tab Layout for Topics.
- Create Polls with Up To 12 Options.
2025-06-04 22:56:02 +04:00
Ilya Fedin
af061125dd Fix Docker build without LTO 2025-06-04 21:33:53 +04:00
John Preston
5c4b1f6638 Show message author to admins in monoforums. 2025-06-04 21:33:27 +04:00
John Preston
ee3d70f879 Fix glitching userpics in monoforum. 2025-06-04 21:05:08 +04:00
John Preston
7dadaa1b28 Save subsection tabs layout to disk. 2025-06-04 19:39:34 +04:00
John Preston
a72782e232 Use server provided default stars count for direct. 2025-06-04 18:42:17 +04:00
John Preston
8654ffb6fb Don't show "Who Viewed" in monoforums. 2025-06-04 18:26:48 +04:00
John Preston
90e445eec9 Don't show notifications from other admins. 2025-06-04 18:26:30 +04:00
John Preston
910b6d8879 Fix unread mark badge in new forums layout. 2025-06-04 18:13:21 +04:00
John Preston
8d1c2f832d Add "Create topic" to new forum view. 2025-06-04 17:59:25 +04:00
John Preston
158d2a4124 Fix possible stack overflow in subsection tabs. 2025-06-04 17:59:10 +04:00
John Preston
66473738d6 Add simple shadow to subsection tabs. 2025-06-04 17:26:23 +04:00
John Preston
6a43107bb2 Fix possible crash in subsection tabs. 2025-06-04 16:52:44 +04:00
John Preston
28e7afa412 Even nicer empty chat. 2025-06-04 16:48:37 +04:00
John Preston
8dc151e14d Fix build on Windows. 2025-06-04 16:21:21 +04:00
John Preston
a330a3f2eb Nicer empty monoforum for non-admins. 2025-06-04 15:45:22 +04:00
Ilya Fedin
8f7195d3b2 Fix macOS action 2025-06-04 11:17:35 +04:00
Ilya Fedin
a4e4502d50 Add missing dependencies to macOS packaged action 2025-06-04 11:17:35 +04:00
GitHub Action
902da90100 Update User-Agent for DNS to Chrome 136.0.0.0. 2025-06-04 10:28:54 +04:00
John Preston
d775760f98 Support nice monoforum userpics. 2025-06-04 10:00:24 +04:00
John Preston
dfb6600104 Fix loading of horizontal avatar strip. 2025-06-04 10:00:24 +04:00
John Preston
41ed487d5e Improve opening ChatWidget at the end. 2025-06-04 10:00:24 +04:00
John Preston
d156de05a5 Allow replying in monoforum while not in it. 2025-06-04 10:00:24 +04:00
John Preston
f4582ddf36 Correctly mark monoforum chats as read. 2025-06-04 10:00:24 +04:00
John Preston
7f7b764f7b Allow ton:// links in webapps. 2025-06-04 10:00:24 +04:00
John Preston
dd8fdfc3d4 Allow forwarding polls to monoforums. 2025-06-04 10:00:24 +04:00
John Preston
6c80d443b9 Better entry point for Direct Messages. 2025-06-04 10:00:24 +04:00
John Preston
cd05586d51 Fix display of pinned messages in sublists. 2025-06-04 10:00:24 +04:00
John Preston
dfc1ec3ccf Support shared media / pins for sublists. 2025-06-04 10:00:23 +04:00
John Preston
ffe6786ad1 Support unread reactions in monoforums. 2025-06-04 10:00:23 +04:00
John Preston
6068678fa1 Improve separate window support. 2025-06-04 10:00:23 +04:00
John Preston
50b761fab2 Remove showing monoforums inside dialogs widget. 2025-06-04 10:00:23 +04:00
John Preston
0d43f16db2 Remove unsupported actions from monoforum menu. 2025-06-04 10:00:23 +04:00
John Preston
3278de9ba1 Support mark as read/unread in sublists. 2025-06-04 10:00:23 +04:00
John Preston
abe1962002 Show context menu for topics in new tabs. 2025-06-04 10:00:23 +04:00
John Preston
5b15f377cd Improve set direct message price box. 2025-06-04 10:00:23 +04:00
John Preston
d0e5ea78a5 Improve topics layout management. 2025-06-04 10:00:23 +04:00
John Preston
853757e611 Fix tabs transfer between chat widgets. 2025-06-04 10:00:22 +04:00
John Preston
c3860cfe72 Improve monoforum top bar status. 2025-06-04 10:00:22 +04:00
John Preston
90b2c077a6 Don't flood with bot requests in monoforums. 2025-06-04 10:00:22 +04:00
John Preston
fdce4bada7 Implement nice topic mode editing. 2025-06-04 10:00:22 +04:00
John Preston
4c8ff1c7ec Disable polls in monoforums, enable in Saved Messages. 2025-06-04 10:00:22 +04:00
John Preston
2a153214f6 Support polls with 12 options. 2025-06-04 10:00:22 +04:00
John Preston
d7c964afc5 Show "Messages" badge for monoforum. 2025-06-04 10:00:22 +04:00
John Preston
5943052cd1 Show badges in new tabs. 2025-06-04 10:00:22 +04:00
John Preston
8512154b45 Implement better horizontal/vertical tabs. 2025-06-04 10:00:22 +04:00
John Preston
0e5419c60b Fix opening forums with tabs. 2025-06-04 10:00:22 +04:00
John Preston
1d26482298 Update API scheme on layer 204. 2025-06-04 10:00:21 +04:00
John Preston
126749f04c Fix build with new MSVC. 2025-06-04 10:00:21 +04:00
John Preston
e0e69ce740 Support vertical tabs somehow. 2025-06-04 10:00:21 +04:00
John Preston
72b57924b7 Correctly load tab slices. 2025-06-04 10:00:21 +04:00
John Preston
fdbdeeb956 Start new tabs for monoforums. 2025-06-04 10:00:21 +04:00
John Preston
3dbdecf73d Make monoforum sender badges float. 2025-06-04 10:00:21 +04:00
John Preston
7dc8943840 Improve monoforum opening. 2025-06-04 10:00:21 +04:00
John Preston
646b852717 Correct rights check in monoforums. 2025-06-04 10:00:21 +04:00
John Preston
075f754a71 Update API scheme on layer 204. 2025-06-04 10:00:21 +04:00
John Preston
f65556acb7 Support drafts in monoforum sublists. 2025-06-04 10:00:21 +04:00
John Preston
b2c01991a6 Support unread state in sublists. 2025-06-04 10:00:21 +04:00
John Preston
4bc5e81513 Update API scheme on layer 204. 2025-06-04 10:00:19 +04:00
John Preston
2b24fe95c2 Update API scheme on layer 204. 2025-06-04 10:00:19 +04:00
John Preston
358e64f2cc Show monoforums as forums in chats list. 2025-06-04 10:00:19 +04:00
John Preston
5dc50b6d96 Respect price of messages to channels. 2025-06-04 10:00:18 +04:00
John Preston
b91a040a32 Update API scheme on layer 204. 2025-06-04 10:00:18 +04:00
John Preston
76db55ff19 Support forwarding to monoforums. 2025-06-04 10:00:18 +04:00
John Preston
e17bf18350 Update API scheme on layer 204. 2025-06-04 10:00:18 +04:00
John Preston
43b4499125 Add monoforum sender bar divider. 2025-06-04 10:00:18 +04:00
John Preston
c6d43a802c Fix sending messages in monoforums. 2025-06-04 10:00:18 +04:00
John Preston
21f8403357 Merge SublistSection into ChatSection. 2025-06-04 10:00:18 +04:00
John Preston
40053e3388 Rename RepliesWidget/Memento to ChatWidget/Memento. 2025-06-04 10:00:14 +04:00
John Preston
abcf7e3a47 Update API scheme & fix monoforum send. 2025-06-04 10:00:14 +04:00
John Preston
f8913bf9b9 Show square userpics for monoforums. 2025-06-04 10:00:14 +04:00
John Preston
51878ab38e Allow opening monoforums. 2025-06-04 10:00:14 +04:00
John Preston
d3f9a84a0a Allow enabling direct messages in channels. 2025-06-04 10:00:14 +04:00
John Preston
23eedb468f Update API scheme to layer 204. 2025-06-04 10:00:14 +04:00
John Preston
9cb89fff45 Fix dropping filter in gifts. 2025-06-04 10:00:14 +04:00
23rd
79f0b22276 Compressed lottie for media forbidden. 2025-06-03 18:30:38 +03:00
23rd
8b2a728a0d Fixed display of loading animation from search in Saved Messages. 2025-06-03 18:28:11 +03:00
23rd
b4120b156e Added nice overlay to recorded round videos. 2025-06-03 17:55:37 +03:00
23rd
1ae3122c20 Added support of suggestion to validate cloud password to settings. 2025-06-03 17:55:37 +03:00
23rd
727acca217 Updated Qt to 5.15.17 on Windows. 2025-06-03 17:55:37 +03:00
23rd
5f8d662d67 Slightly improved of forward declarations in history item. 2025-06-03 17:55:18 +03:00
23rd
81b432140c Added ability to edit caption from box even when file is uploaded. 2025-06-03 17:55:18 +03:00
23rd
adc1ee71a9 Slightly improved box to edit caption of grouped file. 2025-06-03 17:55:18 +03:00
23rd
5ac373d4aa Added simple box to edit caption of single file while it's uploading. 2025-06-03 17:55:18 +03:00
23rd
5b9e24f3f4 Slightly improved box for writing captions to be more generic. 2025-06-03 17:55:18 +03:00
23rd
0e44de2fe3 Slightly improved style of exception button in notifications settings. 2025-06-03 17:55:18 +03:00
23rd
b0125e8165 Slightly improved display of numbers approaching zero in stats charts. 2025-06-03 17:55:18 +03:00
23rd
a532067a93 Fixed dismissing of custom pending suggestions. 2025-06-03 17:55:18 +03:00
Ilya Fedin
f456071c08 Switch Dockerfile to packaged mode 2025-06-03 18:25:15 +04:00
Ilya Fedin
3896f0995c Runtime Implib detection 2025-06-03 18:25:15 +04:00
Ilya Fedin
56ff5808a3 Unify packaged/non-packaged binary name 2025-06-03 18:25:15 +04:00
Ilya Fedin
15c817dd15 Update Qt 6.9.0 -> 6.9.1 2025-06-03 15:01:48 +04:00
Ilya Fedin
7246c3f304 Set cmake OpenGL default to legacy in Dockerfile 2025-06-03 14:11:25 +04:00
Ilya Fedin
e4f59f1ec4 Build only static libraries in Dockerfile 2025-06-03 14:11:25 +04:00
Ilya Fedin
5f0e9538cf Move Implib to Dockerfile 2025-06-03 10:16:58 +04:00
Ilya Fedin
73649128f3 Update cmake_helpers 2025-06-03 09:58:11 +04:00
Ilya Fedin
edc84731ac Change debug cmake flags according to Dockerfile options 2025-06-03 09:58:11 +04:00
Ilya Fedin
108b116b06 Use lld when building without LTO in Dockerfile 2025-06-03 09:58:11 +04:00
Ilya Fedin
dda587dc6f DESKTOP_APP_USE_PACKAGED_LAZY -> DESKTOP_APP_DISABLE_QT_PLUGINS 2025-06-03 09:50:07 +04:00
Ilya Fedin
4a9dd43598 Update tg_owt 2025-06-03 09:41:49 +04:00
Ilya Fedin
a64cfe661a Add missing deps to webrtc Docker layer 2025-06-03 09:41:33 +04:00
Ilya Fedin
7e418a16ae Fix packaged conditions in lib_ffmpeg and Packer 2025-06-03 09:41:22 +04:00
Ilya Fedin
ecf1fa2bbd Get rid of lib prefixes in Docker layers 2025-06-03 09:28:44 +04:00
Ilya Fedin
a6157a34bc Update variable syntax in Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
8e37e66706 Make TOOLSET variable not dependent on jinja in Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
dd6a4931e5 Make QT variable local to the Docker layer 2025-06-03 09:28:44 +04:00
Ilya Fedin
2d000e826b Remove GIT variable from Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
c1028e7408 Remove GIT_FREEDESKTOP variable from Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
28b54fac37 Revert GIT_UPDATE_M4 in Dockerfile
This partially reverts commit 9461095c88.
2025-06-03 09:28:44 +04:00
Ilya Fedin
845fddf5f2 Use enable_language 2025-06-03 09:28:31 +04:00
Ilya Fedin
e3e2a477c1 Proper check for multi-config generator 2025-06-03 09:20:45 +04:00
Ilya Fedin
bf4442ecf5 CMAKE_CONFIG_TYPE doesn't seem to work
This partially reverts commit ae45189436.
2025-06-03 09:20:24 +04:00
AlexeyZavar
3be793032f fix: use usinfobot for username resolving & update README 2025-06-02 15:52:31 +03:00
Crd5
0fab18e8e8
chore: add homebrew as a download source for macOS 2025-06-02 15:48:52 +03:00
Ilya Fedin
ab6375ef2a Update submodules 2025-05-27 10:56:02 +04:00
Ilya Fedin
2535b6e08c Update GCC to 14 on Linux 2025-05-27 10:56:02 +04:00
Ilya Fedin
2f003d416a Update OpenAL to 1.24.3 on Linux 2025-05-27 10:56:02 +04:00
Ilya Fedin
579b358f8b Use system gobject-introspection in Dockerfile 2025-05-27 10:56:02 +04:00
Ilya Fedin
231a583bf7 Build tg_owt in packaged mode in Dockerfile 2025-05-27 10:56:02 +04:00
Ilya Fedin
f0cfbacb4f Install Qt to global prefix in Dockerfile 2025-05-27 10:56:02 +04:00
Ilya Fedin
ae45189436 Set cmake generator, build type and parallel level globally in Dockerfile 2025-05-27 10:56:02 +04:00
Ilya Fedin
ff8292b863 Set build directory for libde265 in Dockerfile 2025-05-27 10:56:02 +04:00
Ilya Fedin
ebe45f73a0 Shorten GIT_UPDATE_M4 2025-05-27 10:56:02 +04:00
kuro neko
c91f41e988
fix: upstream docker build
Same as 88ce676c46
2025-05-26 04:55:51 +03:00
Ilya Fedin
88ce676c46 Use Python 3.11 explicitly in Docker 2025-05-20 18:55:54 +04:00
Ilya Fedin
426cc2798e Revert "Use Python 3.12 in Docker"
This reverts commit 1d2df63652.
2025-05-20 18:55:54 +04:00
Ilya Fedin
1d2df63652 Use Python 3.12 in Docker 2025-05-20 17:59:43 +04:00
556 changed files with 33739 additions and 9869 deletions

42
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,42 @@
name: Docker.
on:
push:
paths:
- '.github/workflows/docker.yml'
- 'Telegram/build/docker/centos_env/**'
jobs:
docker:
name: Ubuntu
runs-on: ubuntu-latest
if: github.ref_name == github.event.repository.default_branch
env:
IMAGE_TAG: ghcr.io/${{ github.repository }}/centos_env:latest
steps:
- name: Clone.
uses: actions/checkout@v4
with:
submodules: recursive
- name: First set up.
run: |
sudo apt update
curl -sSL https://install.python-poetry.org | python3 -
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
tool-cache: true
- name: Docker image build.
run: |
cd Telegram/build/docker/centos_env
poetry install
DEBUG= LTO= poetry run gen_dockerfile | DOCKER_BUILDKIT=1 docker build -t $IMAGE_TAG -
- name: Push the Docker image.
run: docker push $IMAGE_TAG

6
.gitmodules vendored
View file

@ -1,6 +1,3 @@
[submodule "Telegram/ThirdParty/libtgvoip"]
path = Telegram/ThirdParty/libtgvoip
url = https://github.com/telegramdesktop/libtgvoip
[submodule "Telegram/ThirdParty/GSL"]
path = Telegram/ThirdParty/GSL
url = https://github.com/Microsoft/GSL.git
@ -76,9 +73,6 @@
[submodule "Telegram/lib_webview"]
path = Telegram/lib_webview
url = https://github.com/desktop-app/lib_webview.git
[submodule "Telegram/ThirdParty/jemalloc"]
path = Telegram/ThirdParty/jemalloc
url = https://github.com/jemalloc/jemalloc
[submodule "Telegram/ThirdParty/dispatch"]
path = Telegram/ThirdParty/dispatch
url = https://github.com/apple/swift-corelibs-libdispatch

View file

@ -12,19 +12,17 @@ include(cmake/validate_special_target.cmake)
include(cmake/version.cmake)
desktop_app_parse_version(Telegram/build/version)
set(project_langs C CXX)
if (APPLE)
list(APPEND project_langs OBJC OBJCXX)
elseif (LINUX)
list(APPEND project_langs ASM)
endif()
project(Telegram
LANGUAGES ${project_langs}
LANGUAGES C CXX
VERSION ${desktop_app_version_cmake}
DESCRIPTION "AyuGram Desktop"
HOMEPAGE_URL "https://ayugram.one"
)
if (APPLE)
enable_language(OBJC OBJCXX)
endif()
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Telegram)
get_filename_component(third_party_loc "Telegram/ThirdParty" REALPATH)
@ -39,9 +37,7 @@ include(cmake/variables.cmake)
include(cmake/nice_target_sources.cmake)
include(cmake/target_compile_options_if_exists.cmake)
include(cmake/target_link_frameworks.cmake)
include(cmake/target_link_optional_libraries.cmake)
include(cmake/target_link_options_if_exists.cmake)
include(cmake/target_link_static_libraries.cmake)
include(cmake/init_target.cmake)
include(cmake/generate_target.cmake)
include(cmake/nuget.cmake)

View file

@ -30,7 +30,7 @@
### Windows
#### Официальный вариант
#### Официальная версия
Вы можете скачать готовый бинарный файл со вкладки [Releases](https://github.com/AyuGram/AyuGramDesktop/releases) или из
[Телеграм канала](https://t.me/AyuGramReleases).
@ -55,16 +55,40 @@ scoop install ayugram
### macOS
#### Официальная версия
Вы можете скачать подписанный пакет со вкладки [Releases](https://github.com/AyuGram/AyuGramDesktop/releases).
#### Homebrew
```bash
brew install --cask ayugram
```
### Arch Linux
Вы можете установить `ayugram-desktop` из [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
#### Из исходников (рекомендованный способ)
Установите `ayugram-desktop` из [AUR](https://aur.archlinux.org/packages/ayugram-desktop).
#### Готовые бинарники
Установите `ayugram-desktop-bin` из [AUR](https://aur.archlinux.org/packages/ayugram-desktop-bin).
Примечание: данный пакет собирается не нами.
### NixOS
Попробуйте [этот репозиторий](https://github.com/ayugram-port/ayugram-desktop).
### ALT Linux
[Sisyphus](https://packages.altlinux.org/en/sisyphus/srpms/ayugram-desktop/)
### EPM
`epm play ayugram`
### Любой другой Линукс дистрибутив
Следуйте [официальному руководству](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-linux.md).
@ -101,3 +125,7 @@ scoop install ayugram
### Иконки
- [Solar Icon Set](https://www.figma.com/community/file/1166831539721848736)
### Боты
- [TelegramDB](https://t.me/tgdatabase) для получения юзернейма по ID

View file

@ -56,16 +56,40 @@ build by yourself.
### macOS
#### Official
You can download prebuilt macOS package from [Releases tab](https://github.com/AyuGram/AyuGramDesktop/releases).
#### Homebrew
```bash
brew install --cask ayugram
```
### Arch Linux
You can install `ayugram-desktop` from [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
#### From source (recommended)
Install `ayugram-desktop` from [AUR](https://aur.archlinux.org/packages/ayugram-desktop).
#### Prebuilt binaries
Install `ayugram-desktop-bin` from [AUR](https://aur.archlinux.org/packages/ayugram-desktop-bin).
Note: these binaries aren't officially maintained by us.
### NixOS
See [this repository](https://github.com/ayugram-port/ayugram-desktop) for installation manual.
### ALT Linux
[Sisyphus](https://packages.altlinux.org/en/sisyphus/srpms/ayugram-desktop/)
### EPM
`epm play ayugram`
### Any other Linux distro
Follow the [official guide](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-linux.md).
@ -102,3 +126,7 @@ Enjoy using **AyuGram**? Consider sending us a tip!
### Icons
- [Solar Icon Set](https://www.figma.com/community/file/1166831539721848736)
### Bots
- [TelegramDB](https://t.me/tgdatabase) for username lookup by ID

View file

@ -53,9 +53,7 @@ set_target_properties(Telegram PROPERTIES AUTOMOC ON)
target_link_libraries(Telegram
PRIVATE
# tdesktop::lib_tgcalls_legacy
tdesktop::lib_tgcalls
# tdesktop::lib_tgvoip
# Order in this list defines the order of include paths in command line.
# We need to place desktop-app::external_minizip this early to have its
@ -129,8 +127,6 @@ set(ayugram_files
ayu/ui/ayu_logo.h
ayu/ui/utils/ayu_profile_values.cpp
ayu/ui/utils/ayu_profile_values.h
ayu/ui/settings/icon_picker.cpp
ayu/ui/settings/icon_picker.h
ayu/ui/settings/settings_ayu.cpp
ayu/ui/settings/settings_ayu.h
ayu/ui/context_menu/context_menu.cpp
@ -143,10 +139,8 @@ set(ayugram_files
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
ayu/ui/boxes/edit_edited_mark.h
ayu/ui/boxes/edit_mark_box.cpp
ayu/ui/boxes/edit_mark_box.h
ayu/ui/boxes/font_selector.cpp
ayu/ui/boxes/font_selector.h
ayu/ui/boxes/theme_selector_box.cpp
@ -155,6 +149,8 @@ set(ayugram_files
ayu/ui/boxes/message_shot_box.h
ayu/ui/components/image_view.cpp
ayu/ui/components/image_view.h
ayu/ui/components/icon_picker.cpp
ayu/ui/components/icon_picker.h
ayu/libs/json.hpp
ayu/libs/json_ext.hpp
ayu/libs/sqlite/sqlite3.c
@ -170,6 +166,10 @@ set(ayugram_files
ayu/features/streamer_mode/streamer_mode.h
ayu/features/messageshot/message_shot.cpp
ayu/features/messageshot/message_shot.h
ayu/features/forward/ayu_forward.cpp
ayu/features/forward/ayu_forward.h
ayu/features/forward/ayu_sync.cpp
ayu/features/forward/ayu_sync.h
ayu/data/messages_storage.cpp
ayu/data/messages_storage.h
ayu/data/entities.h
@ -209,6 +209,8 @@ PRIVATE
api/api_confirm_phone.h
api/api_credits.cpp
api/api_credits.h
api/api_credits_history_entry.cpp
api/api_credits_history_entry.h
api/api_earn.cpp
api/api_earn.h
api/api_editing.cpp
@ -258,8 +260,12 @@ PRIVATE
api/api_statistics_data_deserialize.h
api/api_statistics_sender.cpp
api/api_statistics_sender.h
api/api_suggest_post.cpp
api/api_suggest_post.h
api/api_text_entities.cpp
api/api_text_entities.h
api/api_todo_lists.cpp
api/api_todo_lists.h
api/api_toggling_media.cpp
api/api_toggling_media.h
api/api_transcribes.cpp
@ -363,6 +369,8 @@ PRIVATE
boxes/edit_caption_box.h
boxes/edit_privacy_box.cpp
boxes/edit_privacy_box.h
boxes/edit_todo_list_box.cpp
boxes/edit_todo_list_box.h
boxes/gift_credits_box.cpp
boxes/gift_credits_box.h
boxes/gift_premium_box.cpp
@ -546,6 +554,7 @@ PRIVATE
core/crash_report_window.h
core/crash_reports.cpp
core/crash_reports.h
core/credits_amount.h
core/deadlock_detector.h
core/file_utilities.cpp
core/file_utilities.h
@ -559,7 +568,6 @@ PRIVATE
core/sandbox.h
core/shortcuts.cpp
core/shortcuts.h
core/stars_amount.h
core/ui_integration.cpp
core/ui_integration.h
core/update_checker.cpp
@ -587,6 +595,8 @@ PRIVATE
data/components/promo_suggestions.h
data/components/recent_peers.cpp
data/components/recent_peers.h
data/components/recent_shared_media_gifts.cpp
data/components/recent_shared_media_gifts.h
data/components/scheduled_messages.cpp
data/components/scheduled_messages.h
data/components/sponsored_messages.cpp
@ -731,6 +741,8 @@ PRIVATE
data/data_streaming.h
data/data_thread.cpp
data/data_thread.h
data/data_todo_list.cpp
data/data_todo_list.h
data/data_types.cpp
data/data_types.h
data/data_unread_value.cpp
@ -831,6 +843,8 @@ PRIVATE
history/view/controls/history_view_draft_options.h
history/view/controls/history_view_forward_panel.cpp
history/view/controls/history_view_forward_panel.h
history/view/controls/history_view_suggest_options.cpp
history/view/controls/history_view_suggest_options.h
history/view/controls/history_view_ttl_button.cpp
history/view/controls/history_view_ttl_button.h
history/view/controls/history_view_voice_record_bar.cpp
@ -892,8 +906,12 @@ PRIVATE
history/view/media/history_view_sticker_player_abstract.h
history/view/media/history_view_story_mention.cpp
history/view/media/history_view_story_mention.h
history/view/media/history_view_suggest_decision.cpp
history/view/media/history_view_suggest_decision.h
history/view/media/history_view_theme_document.cpp
history/view/media/history_view_theme_document.h
history/view/media/history_view_todo_list.cpp
history/view/media/history_view_todo_list.h
history/view/media/history_view_unique_gift.cpp
history/view/media/history_view_unique_gift.h
history/view/media/history_view_userpic_suggestion.cpp
@ -918,6 +936,8 @@ PRIVATE
history/view/history_view_bottom_info.h
history/view/history_view_chat_preview.cpp
history/view/history_view_chat_preview.h
history/view/history_view_chat_section.cpp
history/view/history_view_chat_section.h
history/view/history_view_contact_status.cpp
history/view/history_view_contact_status.h
history/view/history_view_context_menu.cpp
@ -952,8 +972,6 @@ PRIVATE
history/view/history_view_pinned_tracker.h
history/view/history_view_quick_action.cpp
history/view/history_view_quick_action.h
history/view/history_view_replies_section.cpp
history/view/history_view_replies_section.h
history/view/history_view_reply.cpp
history/view/history_view_reply.h
history/view/history_view_requests_bar.cpp
@ -970,8 +988,8 @@ PRIVATE
history/view/history_view_sponsored_click_handler.h
history/view/history_view_sticker_toast.cpp
history/view/history_view_sticker_toast.h
history/view/history_view_sublist_section.cpp
history/view/history_view_sublist_section.h
history/view/history_view_subsection_tabs.cpp
history/view/history_view_subsection_tabs.h
history/view/history_view_text_helper.cpp
history/view/history_view_text_helper.h
history/view/history_view_transcribe_button.cpp
@ -1321,6 +1339,8 @@ PRIVATE
media/view/media_view_playback_controls.h
media/view/media_view_playback_progress.cpp
media/view/media_view_playback_progress.h
media/view/media_view_playback_sponsored.cpp
media/view/media_view_playback_sponsored.h
media/system_media_controls_manager.h
media/system_media_controls_manager.cpp
menu/menu_antispam_validator.cpp
@ -1526,6 +1546,8 @@ PRIVATE
settings/cloud_password/settings_cloud_password_start.h
settings/cloud_password/settings_cloud_password_step.cpp
settings/cloud_password/settings_cloud_password_step.h
settings/cloud_password/settings_cloud_password_validate_icon.cpp
settings/cloud_password/settings_cloud_password_validate_icon.h
settings/settings_active_sessions.cpp
settings/settings_active_sessions.h
settings/settings_advanced.cpp
@ -1946,11 +1968,7 @@ else()
set(bundle_identifier "one.ayugram.AyuGramDesktop")
endif()
set(bundle_entitlements "Telegram.entitlements")
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
set(output_name "ayugram-desktop")
else()
set(output_name "AyuGram")
endif()
set(output_name "AyuGram")
endif()
if (CMAKE_GENERATOR STREQUAL Xcode)
@ -1992,8 +2010,9 @@ PRIVATE
G_LOG_DOMAIN="Telegram"
)
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (APPLE
OR "${CMAKE_GENERATOR}" STREQUAL "Ninja Multi-Config"
OR is_multi_config
OR NOT CMAKE_EXECUTABLE_SUFFIX STREQUAL ""
OR NOT "${output_name}" STREQUAL "AyuGram")
set(output_folder ${CMAKE_BINARY_DIR})
@ -2010,8 +2029,67 @@ if (MSVC)
)
target_link_options(Telegram
PRIVATE
/DELAYLOAD:secur32.dll
/DELAYLOAD:winmm.dll
/DELAYLOAD:ws2_32.dll
/DELAYLOAD:user32.dll
/DELAYLOAD:gdi32.dll
/DELAYLOAD:advapi32.dll
/DELAYLOAD:avrt.dll
/DELAYLOAD:shell32.dll
/DELAYLOAD:ole32.dll
/DELAYLOAD:oleaut32.dll
/DELAYLOAD:shlwapi.dll
/DELAYLOAD:iphlpapi.dll
/DELAYLOAD:gdiplus.dll
/DELAYLOAD:version.dll
/DELAYLOAD:dwmapi.dll
/DELAYLOAD:uxtheme.dll
/DELAYLOAD:crypt32.dll
/DELAYLOAD:bcrypt.dll
/DELAYLOAD:netapi32.dll
/DELAYLOAD:imm32.dll
/DELAYLOAD:userenv.dll
/DELAYLOAD:wtsapi32.dll
/DELAYLOAD:propsys.dll
)
if (QT_VERSION GREATER 6)
if (NOT build_winarm)
target_link_options(Telegram PRIVATE
/DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
)
endif()
target_link_options(Telegram
PRIVATE
/DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll
/DELAYLOAD:API-MS-Win-Core-File-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-LibraryLoader-l1-2-0.dll
/DELAYLOAD:API-MS-Win-Core-Localization-l1-2-0.dll
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-1.dll
/DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib
/DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll
# /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
# /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll # We shadowed GetDpiForMonitor
/DELAYLOAD:authz.dll # Authz.lib
/DELAYLOAD:comdlg32.dll
/DELAYLOAD:dwrite.dll # DWrite.lib
/DELAYLOAD:dxgi.dll # DXGI.lib
/DELAYLOAD:d3d9.dll # D3D9.lib
/DELAYLOAD:d3d11.dll # D3D11.lib
/DELAYLOAD:d3d12.dll # D3D12.lib
/DELAYLOAD:setupapi.dll # SetupAPI.lib
/DELAYLOAD:winhttp.dll
)
endif()
endif()
target_prepare_qrc(Telegram)
@ -2042,6 +2120,22 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
base/platform/win/base_windows_safe_library.h
)
target_include_directories(Updater PRIVATE ${lib_base_loc})
if (MSVC)
target_link_libraries(Updater
PRIVATE
delayimp
)
target_link_options(Updater
PRIVATE
/DELAYLOAD:user32.dll
/DELAYLOAD:advapi32.dll
/DELAYLOAD:shell32.dll
/DELAYLOAD:ole32.dll
/DELAYLOAD:shlwapi.dll
)
else()
target_link_options(Updater PRIVATE -municode)
endif()
elseif (APPLE)
add_custom_command(TARGET Updater
PRE_LINK
@ -2070,6 +2164,10 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
desktop-app::external_openssl
)
if (DESKTOP_APP_USE_PACKAGED)
target_compile_definitions(Packer PRIVATE PACKER_USE_PACKAGED)
endif()
set_target_properties(Packer PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
endif()
elseif (build_winstore)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon / Input / input_paid</title>
<g id="Icon-/-Input-/-input_paid" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M20,9.8 C25.6299826,9.8 30.2,14.2730045 30.2,19.7983329 C30.2,25.3236612 25.6299826,29.7966657 20,29.7966657 C18.7457032,29.7966657 17.522531,29.57429 16.3765194,29.1472418 L16.049,29.018 L15.8675895,29.1274403 L15.6273764,29.2612632 C14.545782,29.8404011 13.0955737,30.1473058 11.2731839,30.1996583 C10.8153842,30.2127839 10.4336239,29.8523042 10.4204985,29.3945045 C10.4177264,29.2978179 10.4318913,29.2013918 10.4663408,29.0979123 C10.9468917,27.7307176 11.2958938,26.5818971 11.5130707,25.6565167 L11.566,25.42 L11.5361505,25.3785138 C10.4824637,23.8473989 9.87852612,22.0565089 9.80714989,20.1756532 L9.8,19.7983329 C9.8,14.2730045 14.3700174,9.8 20,9.8 Z M20,11.2 C15.1365724,11.2 11.2,15.0530063 11.2,19.7983329 C11.2,21.6384229 11.7922255,23.3893508 12.8759186,24.8453165 L13.0610907,25.0940992 L13.001223,25.3983974 C12.8243697,26.2973155 12.5137714,27.4099145 12.0719423,28.73207 L12.064,28.754 L12.2244984,28.7405395 C13.2682683,28.6413859 14.1190062,28.4334572 14.7754263,28.1231964 L14.9665215,28.0270547 C15.164827,27.9208723 15.3780604,27.7932923 15.605736,27.6441968 L15.9287098,27.4326945 L16.2799121,27.5930136 C17.4341359,28.1199012 18.6962936,28.3966657 20,28.3966657 C24.8634276,28.3966657 28.8,24.5436594 28.8,19.7983329 C28.8,15.0530063 24.8634276,11.2 20,11.2 Z" id="Shape---" fill="#FFFFFF" fill-rule="nonzero"></path>
<path d="M20.2258661,25.6815247 C20.5071894,25.6815247 20.874026,25.4574745 20.874026,25.0631159 L20.874026,24.4093038 C22.5557139,24.2186972 23.4454545,23.1802196 23.4454545,21.6685117 C23.4454545,20.3671283 22.7015108,19.5784112 21.1573587,19.2234884 L19.8882782,18.9211469 C19.0943214,18.7371128 18.7067205,18.3558996 18.7067205,17.7972249 C18.7067205,17.1268153 19.2568638,16.6404397 20.1195884,16.6404397 C20.8197708,16.6404397 21.3073978,16.8902001 21.8512894,17.5277465 C22.126361,17.8300881 22.3389164,17.941823 22.6264913,17.941823 C22.9765824,17.941823 23.2454024,17.68549 23.2454024,17.3042767 C23.2454024,16.9362087 23.0390987,16.5352774 22.6890076,16.1737821 C22.2263871,15.713697 21.7022327,15.4113555 20.9207792,15.3061932 L20.9207792,14.6509738 C20.9207792,14.2631879 20.5009377,13.9363049 20.2133629,13.9363049 C19.9320396,13.9363049 19.5493506,14.2566152 19.5493506,14.6509738 L19.5493506,15.2864752 C17.930179,15.4442187 17.0312842,16.4629784 17.0312842,17.9221051 C17.0312842,19.1971979 17.7752279,20.0450688 19.2005991,20.3802736 L20.4696796,20.6891878 C21.3949206,20.9192303 21.7762699,21.2610078 21.7762699,21.8394004 C21.7762699,22.5886817 21.219875,23.061912 20.2258661,23.061912 C19.4819224,23.061912 18.8630112,22.766143 18.3003647,22.1285967 C17.9815316,21.7933919 17.8064861,21.7210928 17.5689242,21.7210928 C17.1875749,21.7210928 16.9,21.9774259 16.9,22.417793 C16.9,22.8055789 17.1125554,23.2065101 17.4939047,23.5548602 C17.9940349,24.0346632 18.7115432,24.3238594 19.5805195,24.4093038 L19.5805195,25.0565432 C19.5805195,25.4509018 19.9382912,25.6815247 20.2258661,25.6815247 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon / Filled / paid_approve</title>
<g id="Icon-/-Filled-/-paid_approve" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M12,4.5 C16.14,4.5 19.5,7.86 19.5,12 C19.5,16.14 16.14,19.5 12,19.5 C7.86,19.5 4.5,16.14 4.5,12 C4.5,7.86 7.86,4.5 12,4.5 Z M15.7577636,9.89127556 C15.4992394,9.62810439 15.0763217,9.62433727 14.8131506,9.88286145 L10.7479688,13.8761719 L9.18684944,12.3424898 C8.92367827,12.0839656 8.50076063,12.0877327 8.24223645,12.3509039 C7.98371227,12.6140751 7.98747939,13.0369927 8.25065056,13.2955169 L10.204967,15.2153247 C10.5064723,15.5115061 10.9896874,15.5115061 11.2911927,15.2153247 L15.7493494,10.8358886 C16.0125206,10.5773644 16.0162877,10.1544467 15.7577636,9.89127556 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 999 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon / Filled / paid_decline</title>
<g id="Icon-/-Filled-/-paid_decline" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M12,4.5 C16.1421356,4.5 19.5,7.85786438 19.5,12 C19.5,16.1421356 16.1421356,19.5 12,19.5 C7.85786438,19.5 4.5,16.1421356 4.5,12 C4.5,7.85786438 7.85786438,4.5 12,4.5 Z M9.73375908,8.63964245 C9.43162712,8.33751049 8.94177442,8.33751049 8.63964245,8.63964245 L8.59853606,8.68404063 C8.33819559,8.98800138 8.35189772,9.44601435 8.63964245,9.73375908 L10.9059783,12 L8.63964245,14.2662409 C8.33751049,14.5683729 8.33751049,15.0582256 8.63964245,15.3603575 L8.68404063,15.4014639 C8.98800138,15.6618044 9.44601435,15.6481023 9.73375908,15.3603575 L12,13.0936701 L14.2662409,15.3603575 C14.5683729,15.6624895 15.0582256,15.6624895 15.3603575,15.3603575 L15.4014639,15.3159594 C15.6618044,15.0119986 15.6481023,14.5539856 15.3603575,14.2662409 L13.0936701,12 L15.3603575,9.73375908 C15.6624895,9.43162712 15.6624895,8.94177442 15.3603575,8.63964245 L15.3159594,8.59853606 C15.0119986,8.33819559 14.5539856,8.35189772 14.2662409,8.63964245 L12,10.9059783 L9.73375908,8.63964245 Z" id="Shape" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon / Filled / paid_edit</title>
<g id="Icon-/-Filled-/-paid_edit" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M13.5029106,7.64699152 C13.6056531,7.5472623 13.7697888,7.549705 13.869518,7.65244745 L16.2610253,10.1162122 C16.3593492,10.2175069 16.3585592,10.3788499 16.259248,10.4791768 L7.97687653,18.8462593 C7.87948817,18.9446437 7.74680196,19 7.6083679,19 L5.51851849,19 C5.23214864,19 5,18.7678513 5,18.4814815 L5,16.3683223 C5,16.2308422 5.05459809,16.0989896 5.15178971,16.0017551 L13.5029106,7.64699152 Z M16.0299869,5.19856593 C16.3408365,4.91998643 16.8161619,4.93645109 17.1069903,5.23587194 L18.7801411,6.95845528 C19.073802,7.26079221 19.0732123,7.74202152 18.7788114,8.04363789 L17.6449122,9.20518682 C17.5421105,9.3048549 17.3779763,9.30231456 17.2783082,9.19951281 L14.8031149,6.64621027 C14.707554,6.53957983 14.7165277,6.3756714 14.8231582,6.28011055 L16.0299869,5.19856593 Z" id="Shape" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 470 B

View file

Before

Width:  |  Height:  |  Size: 899 B

After

Width:  |  Height:  |  Size: 899 B

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon / Filled / checklist</title>
<g id="Icon-/-Filled-/-checklist" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M12,4 C16.416,4 20,7.584 20,12 C20,16.416 16.416,20 12,20 C7.584,20 4,16.416 4,12 C4,7.584 7.584,4 12,4 Z M16.4818432,9.54348643 C16.1872637,9.22288416 15.7053635,9.21829494 15.4054889,9.53323613 L10.7733532,14.3979954 L8.99451107,12.5296222 C8.69463652,12.2146811 8.21273632,12.2192703 7.91815685,12.5398725 C7.62357737,12.8604748 7.62786989,13.3756846 7.92774444,13.6906258 L10.1546212,16.0293878 C10.4981762,16.3902041 11.0487833,16.3902041 11.3923383,16.0293878 L16.4722556,10.6942397 C16.7721301,10.3792985 16.7764226,9.8640887 16.4818432,9.54348643 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 975 B

View file

@ -1,10 +1,10 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1067_20)">
<path d="M7.58588 9.73341C8.52939 10.6769 9.41338 12.269 10.085 13.6905C10.5357 14.6445 12.0164 14.6078 12.3503 13.6068L15.8086 3.23922C16.1618 2.1804 15.1541 1.17304 14.0954 1.52654L3.74092 4.98387C2.73902 5.3184 2.70382 6.80252 3.65913 7.2532C5.0706 7.91907 6.64716 8.79469 7.58588 9.73341Z" fill="white"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1362_52)">
<path d="M6.92088 9.06526C7.86439 10.0088 8.74838 11.6009 9.41997 13.0223C9.87073 13.9764 11.3514 13.9397 11.6853 12.9387L15.1436 2.57106C15.4968 1.51225 14.4891 0.504885 13.4304 0.858384L3.07592 4.31571C2.07402 4.65025 2.03882 6.13437 2.99412 6.58505C4.4056 7.25092 5.98216 8.12654 6.92088 9.06526Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1067_20">
<rect width="16" height="16.0037" fill="white" transform="translate(0.665001 0.668152)"/>
<clipPath id="clip0_1362_52">
<rect width="16" height="16.0037" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 566 B

View file

@ -164,11 +164,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_chat_status_members_online" = "{members_count}, {online_count}";
"lng_chat_status_subscribers#one" = "{count} subscriber";
"lng_chat_status_subscribers#other" = "{count} subscribers";
"lng_chat_status_direct" = "Direct messages";
"lng_channel_status" = "channel";
"lng_group_status" = "group";
"lng_scam_badge" = "SCAM";
"lng_fake_badge" = "FAKE";
"lng_direct_badge" = "DIRECT";
"lng_remember" = "Remember this choice";
@ -432,6 +434,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dlg_new_channel_name" = "Channel name";
"lng_dlg_new_bot_name" = "Bot name";
"lng_no_chats" = "Your chats will be here";
"lng_no_conversations" = "You have no\nconversations yet.";
"lng_no_conversations_button" = "New Message";
"lng_no_conversations_subtitle" = "Your contacts on Telegram";
"lng_no_chats_filter" = "No chats currently belong to this folder.";
"lng_no_saved_sublists" = "You can save messages from other chats here.";
"lng_contacts_loading" = "Loading...";
@ -840,6 +845,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}";
"lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do";
"lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}";
"lng_settings_suggestion_password_title" = "Your password";
"lng_settings_suggestion_password_about" = "Your account is protected by 2-Step Veritifaction. Do you still remember your password?";
"lng_settings_suggestion_password_yes" = "Yes, definitely";
"lng_settings_suggestion_password_no" = "Not sure";
"lng_settings_suggestion_password_step_input_title" = "Enter your password";
"lng_settings_suggestion_password_step_input_about" = "Do you still remember your password?";
"lng_settings_suggestion_password_step_finish_title" = "Perfect!";
"lng_settings_suggestion_password_step_finish_about" = "You still remember your password.";
"lng_settings_power_menu" = "Battery and Animations";
"lng_settings_power_title" = "Power Usage";
@ -1152,6 +1165,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_proxy_menu_delete" = "Delete";
"lng_proxy_menu_restore" = "Restore";
"lng_proxy_edit_share" = "Share";
"lng_proxy_edit_share_qr_box_title" = "Share proxy with QR code";
"lng_proxy_edit_share_list_button" = "Share Proxy List";
"lng_proxy_edit_share_list_toast" = "Proxy List copied to clipboard.";
"lng_proxy_address_label" = "Socket address";
"lng_proxy_credentials_optional" = "Credentials (optional)";
"lng_proxy_credentials" = "Credentials";
@ -1190,6 +1206,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_faq_link" = "https://telegram.org/faq#general-questions";
"lng_settings_features" = "Telegram Features";
"lng_settings_credits" = "My Stars";
"lng_settings_currency" = "My TON";
"lng_settings_logout" = "Log out";
"lng_sure_logout" = "Are you sure you want to log out?";
@ -1481,6 +1498,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_hide_participants_about" = "Switch this on to hide the list of members in this group. Admins will remain visible.";
"lng_profile_view_channel" = "View Channel";
"lng_profile_view_discussion" = "View discussion";
"lng_profile_direct_messages" = "Direct messages";
"lng_profile_join_channel" = "Join Channel";
"lng_profile_join_group" = "Join Group";
"lng_profile_apply_to_join_group" = "Apply to Join Group";
@ -1878,6 +1896,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_linked_channel_posted" = "All new posts from this channel are forwarded to the group.";
"lng_manage_discussion_group_warning" = "\"Chat history for new members\" will be switched to **Visible**.";
"lng_manage_monoforum" = "Direct Messages";
"lng_manage_monoforum_off" = "Off";
"lng_manage_monoforum_free" = "Free";
"lng_manage_monoforum_allow" = "Allow Channel Messages";
"lng_manage_monoforum_price" = "Price for each message";
"lng_manage_monoforum_about" = "Allow users to send messages to your channel, with the option to charge a fee for each message.";
"lng_manage_monoforum_price_about" = "Your channel will receive {percent} of the selected fee ({amount}) for each incoming message.";
"lng_manage_history_visibility_title" = "Chat history for new members";
"lng_manage_history_visibility_shown" = "Visible";
"lng_manage_history_visibility_shown_about" = "New members will see messages that were sent before they joined.";
@ -2061,6 +2087,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_changed_title" = "{from} changed group name to «{title}»";
"lng_action_changed_title_channel" = "Channel name was changed to «{title}»";
"lng_action_created_chat" = "{from} created the group «{title}»";
"lng_action_created_monoforum" = "Direct messages were enabled in this channel.";
"lng_action_ttl_changed" = "{from} set messages to auto-delete in {duration}";
"lng_action_ttl_changed_you" = "You set messages to auto-delete in {duration}";
"lng_action_ttl_changed_channel" = "Messages in this channel will be automatically deleted after {duration}";
@ -2168,6 +2195,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_premium_months#other" = "{count} Months Premium";
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
"lng_action_gift_refunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
"lng_action_gift_got_ton" = "Use TON to suggest posts to channels.";
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
"lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile.";
"lng_action_suggested_photo_button" = "View Photo";
@ -2235,6 +2263,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_message_price_free" = "Messages are now free in this group.";
"lng_action_message_price_paid#one" = "Messages now cost {count} Star each in this group.";
"lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group.";
"lng_action_direct_messages_enabled" = "Channel enabled Direct Messages.";
"lng_action_direct_messages_paid#one" = "Channel allows Direct Messages for {count} Star each.";
"lng_action_direct_messages_paid#other" = "Channel allows Direct Messages for {count} Stars each.";
"lng_action_direct_messages_disabled" = "Channel disabled Direct Messages.";
"lng_action_todo_marked_done" = "{from} marked {tasks} as done.";
"lng_action_todo_marked_done_self" = "You marked {tasks} as done.";
"lng_action_todo_marked_not_done" = "{from} marked {tasks} as not done.";
"lng_action_todo_marked_not_done_self" = "You marked {tasks} as not done.";
"lng_action_todo_added" = "{from} added {tasks} to the list.";
"lng_action_todo_added_self" = "You added {tasks} to the list.";
"lng_action_todo_tasks_fallback#one" = "task";
"lng_action_todo_tasks_fallback#other" = "{count} tasks";
"lng_action_todo_tasks_and_one" = "{tasks}, {task}";
"lng_action_todo_tasks_and_last" = "{tasks} and {task}";
"lng_action_suggest_success_stars#one" = "{from} has received {count} Star for publishing post.";
"lng_action_suggest_success_stars#other" = "{from} has received {count} Stars for publishing post.";
"lng_action_suggest_success_ton#one" = "{from} has received {count} TON for publishing post.";
"lng_action_suggest_success_ton#other" = "{from} has received {count} TON for publishing post.";
"lng_action_suggest_refund_user" = "User refunded the Stars so that post was deleted.";
"lng_action_suggest_refund_admin" = "Admin deleted the post early so that the price was refunded to the user.";
"lng_action_post_rejected" = "The post was rejected.";
"lng_action_not_enough_funds" = "Transaction failed.";
"lng_you_paid_stars#one" = "You paid {count} Star.";
"lng_you_paid_stars#other" = "You paid {count} Stars.";
@ -2602,6 +2652,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
"lng_premium_summary_subtitle_filter_tags" = "Tag Your Chats";
"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list.";
"lng_premium_summary_subtitle_todo_lists" = "Checklists";
"lng_premium_summary_about_todo_lists" = "Plan, assign, and complete tasks - seamlessly and efficiently.";
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
"lng_premium_summary_button" = "Subscribe for {cost} per month";
@ -2732,6 +2784,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_title" = "Telegram Stars";
"lng_credits_summary_about" = "Buy Stars to unlock content and services in miniapps on Telegram.";
"lng_credits_currency_summary_title" = "TON Balance";
"lng_credits_currency_summary_about" = "Offer TON to submit post suggestions to channels on Telegram.";
"lng_credits_currency_summary_subtitle" = "You can withdraw your TON using Fragment.";
"lng_credits_currency_summary_in_button" = "Top-up via Fragment";
"lng_credits_currency_summary_in_subtitle" = "You can top-up your TON balance via Fragment.";
"lng_credits_summary_options_subtitle" = "Choose package";
"lng_credits_summary_options_credits#one" = "{count} Star";
"lng_credits_summary_options_credits#other" = "{count} Stars";
@ -2755,8 +2812,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_premium_gift_duration" = "Duration";
"lng_credits_more_options" = "More Options";
"lng_credits_balance_me" = "your balance";
"lng_credits_buy_button" = "Buy More Stars";
"lng_credits_balance_me_count" = "Your balance: {emoji} {amount}";
"lng_credits_buy_button" = "Top Up Balance";
"lng_credits_topup_button" = "{emoji} Top Up Balance";
"lng_credits_buy_button_short" = "Top Up";
"lng_credits_stats_button_short" = "Stats";
"lng_credits_stats_button" = "View Statistics";
"lng_credits_gift_button" = "Gift Stars to Friends";
"lng_credits_earn_button" = "Earn Stars from Mini Apps";
"lng_credits_box_out_title" = "Confirm Your Purchase";
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
@ -2803,6 +2866,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
"lng_credits_box_history_entry_gift_transfer" = "Gift Transfer";
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
"lng_credits_box_history_entry_gift_released" = "released by {name}";
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
@ -2810,6 +2874,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_gift_examples" = "Examples";
"lng_credits_box_history_entry_ads" = "Ads Platform";
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
"lng_credits_box_history_entry_currency_in" = "TON Top-Up";
"lng_credits_box_history_entry_api" = "Paid Broadcast";
"lng_credits_box_history_entry_floodskip_about#one" = "{count} Message";
"lng_credits_box_history_entry_floodskip_about#other" = "{count} Messages";
@ -2873,6 +2938,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
"lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}.";
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_enough" = "You have enough stars at the moment. {link}";
@ -3196,6 +3262,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_feature_transcribe" = "Voice-to-Text Conversion";
"lng_feature_autotranslate" = "Autotranslation of Messages";
"lng_edit_topics_enable" = "Enable Topics";
"lng_edit_topics_about" = "The group chat will be divided into topics created by admins or users.";
"lng_edit_topics_layout" = "Topics layout";
"lng_edit_topics_layout_about" = "Choose how topics appear for all members.";
"lng_edit_topics_tabs" = "Tabs";
"lng_edit_topics_list" = "List";
"lng_giveaway_new_title" = "Boosts via Gifts";
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
"lng_giveaway_new_about_group" = "Get more boosts for your group by gifting Premium to your members.";
@ -3432,6 +3505,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram.";
"lng_gift_until" = "Until";
"lng_gift_ton_amount#one" = "{count} TON";
"lng_gift_ton_amount#other" = "{count} TON";
"lng_gift_premium_or_stars" = "Gift Premium or Stars";
"lng_gift_premium_subtitle" = "Gift Premium";
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
@ -3509,12 +3585,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
"lng_gift_channel_title" = "Send a Gift";
"lng_gift_channel_about" = "Select a gift to show appreciation for {name}.";
"lng_gift_released_by" = "Released by {name}";
"lng_gift_unique_owner" = "Owner";
"lng_gift_unique_address_copied" = "Address copied to clipboard.";
"lng_gift_unique_status" = "Status";
"lng_gift_unique_status_non" = "Non-Unique";
"lng_gift_unique_status_upgrade" = "upgrade";
"lng_gift_unique_number" = "Collectible #{index}";
"lng_gift_unique_number_by" = "Collectible #{index} by {name}";
"lng_gift_unique_model" = "Model";
"lng_gift_unique_backdrop" = "Backdrop";
"lng_gift_unique_symbol" = "Symbol";
@ -3758,6 +3836,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_in_dlg_sticker_emoji" = "{emoji} Sticker";
"lng_in_dlg_poll" = "Poll";
"lng_in_dlg_story" = "Story";
"lng_in_dlg_todo_list" = "Checklist";
"lng_in_dlg_story_expired" = "Expired story";
"lng_in_dlg_media_count#one" = "{count} media";
"lng_in_dlg_media_count#other" = "{count} media";
@ -3847,7 +3926,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_send_anonymous_ph" = "Send anonymously...";
"lng_story_reply_ph" = "Reply privately...";
"lng_story_comment_ph" = "Comment story...";
"lng_message_paid_ph" = "Message for {amount}";
"lng_message_stars_ph#one" = "Message for {count} Star";
"lng_message_stars_ph#other" = "Message for {count} Stars";
"lng_send_text_no" = "Text not allowed.";
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
"lng_send_text_type_and_last" = "{types} and {last}";
@ -4183,9 +4263,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_to_msg" = "Go To Message";
"lng_context_reply_msg" = "Reply";
"lng_context_quote_and_reply" = "Quote & Reply";
"lng_context_reply_to_task" = "Reply to Task";
"lng_context_edit_msg" = "Edit";
"lng_context_add_factcheck" = "Add Fact Check";
"lng_context_edit_factcheck" = "Edit Fact Check";
"lng_context_add_offer" = "Add Offer";
"lng_context_forward_msg" = "Forward";
"lng_context_send_now_msg" = "Send Now";
"lng_context_reschedule" = "Reschedule";
@ -4197,6 +4279,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_pin_msg" = "Pin";
"lng_context_unpin_msg" = "Unpin";
"lng_context_cancel_upload" = "Cancel Upload";
"lng_context_upload_edit_caption" = "Edit Caption";
"lng_context_copy_selected" = "Copy Selected Text";
"lng_context_copy_selected_items" = "Copy Selected as Text";
"lng_context_forward_selected" = "Forward Selected";
@ -4220,6 +4303,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_seen_reacted#other" = "{count} Reacted";
"lng_context_seen_reacted_none" = "Nobody Reacted";
"lng_context_seen_reacted_all" = "Show All Reactions";
"lng_context_sent_by" = "Sent by {user}";
"lng_context_set_as_quick" = "Set As Quick";
"lng_context_filter_by_tag" = "Filter by Tag";
"lng_context_tag_add_name" = "Add Name";
@ -4235,6 +4319,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_edit_shortcut" = "Edit Shortcut";
"lng_context_delete_shortcut" = "Delete Quick Reply";
"lng_context_gift_send" = "Send Another Gift";
"lng_context_charge_fee" = "Charge Fee";
"lng_context_remove_fee" = "Remove Fee";
"lng_context_fee_now" = "{name} pays {amount} per message.";
"lng_context_fee_free" = "{name} can send messages for free.";
"lng_add_tag_about" = "Tag this message with an emoji for quick search.";
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
@ -4366,6 +4454,114 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
"lng_preview_reply_to" = "Reply to {name}";
"lng_preview_reply_to_quote" = "Reply to quote from {name}";
"lng_preview_reply_to_task" = "Reply to task from {title}";
"lng_suggest_bar_title" = "Suggest a Post Below";
"lng_suggest_bar_text" = "Click to offer a price for publishing.";
"lng_suggest_bar_priced" = "{amount} for publishing anytime.";
"lng_suggest_bar_dated" = "Publish on {date}";
"lng_suggest_options_title" = "Suggest a Message";
"lng_suggest_options_change" = "Suggest Changes";
"lng_suggest_options_stars_offer" = "Offer Stars";
"lng_suggest_options_stars_request" = "Request Stars";
"lng_suggest_options_stars_price" = "Enter Price in Stars";
"lng_suggest_options_stars_price_about" = "Choose how many Stars to pay to publish this message.";
"lng_suggest_options_ton_offer" = "Offer TON";
"lng_suggest_options_ton_request" = "Request TON";
"lng_suggest_options_ton_price" = "Enter Price in TON";
"lng_suggest_options_ton_price_about" = "Choose how many TON to pay to publish this message.";
"lng_suggest_options_date" = "Time";
"lng_suggest_options_date_any" = "Anytime";
"lng_suggest_options_date_publish" = "Publish";
"lng_suggest_options_date_now" = "Publish Now";
"lng_suggest_options_date_about" = "Select the date and time you want the message to be published. The post will remain available for at least 24 hours from this date.";
"lng_suggest_options_you_get_stars#one" = "You will receive {count} Star ({percent}) for publishing this post.";
"lng_suggest_options_you_get_stars#other" = "You will receive {count} Stars ({percent}) for publishing this post.";
"lng_suggest_options_you_get_ton#one" = "You will receive {count} TON ({percent}) for publishing this post.";
"lng_suggest_options_you_get_ton#other" = "You will receive {count} TON ({percent}) for publishing this post.";
"lng_suggest_options_stars_warning" = "Transactions in **Stars** may be reversed by the payment provider within **21** days. Only accept Stars from people you trust.";
"lng_suggest_options_offer" = "Offer {amount}";
"lng_suggest_options_offer_free" = "Offer for Free";
"lng_suggest_options_update" = "Update Terms";
"lng_suggest_options_update_date" = "Update Time";
"lng_suggest_action_decline" = "Decline";
"lng_suggest_action_accept" = "Accept";
"lng_suggest_action_change" = "Suggest Changes";
"lng_suggest_action_your" = "You suggest to post this message.";
"lng_suggest_action_his" = "{from} suggests to post this message.";
"lng_suggest_action_price_label" = "Price";
"lng_suggest_action_price_free" = "Free";
"lng_suggest_action_time_label" = "Time";
"lng_suggest_action_time_any" = "Anytime";
"lng_suggest_action_agreement" = "Agreement reached!";
"lng_suggest_action_agree_date" = "The post will be automatically published on {channel} {date}.";
"lng_suggest_action_your_charged_stars#one" = "You have been charged **{count} Star**.";
"lng_suggest_action_your_charged_stars#other" = "You have been charged **{count} Stars**.";
"lng_suggest_action_your_charged_ton#one" = "You have been charged **{count} TON**.";
"lng_suggest_action_your_charged_ton#other" = "You have been charged **{count} TON**.";
"lng_suggest_action_his_charged_stars#one" = "{from} has been charged **{count} Star**.";
"lng_suggest_action_his_charged_stars#other" = "{from} has been charged **{count} Stars**.";
"lng_suggest_action_his_charged_ton#one" = "{from} has been charged **{count} TON**.";
"lng_suggest_action_his_charged_ton#other" = "{from} has been charged **{count} TON**.";
"lng_suggest_action_agree_receive_stars" = "{channel} will receive the Stars once the post has been live for 24 hours.";
"lng_suggest_action_agree_receive_ton" = "{channel} will receive TON once the post has been live for 24 hours.";
"lng_suggest_action_agree_removed_stars" = "If {channel} removes the post before it has been live for 24 hours, the Stars will be refunded.";
"lng_suggest_action_agree_removed_ton" = "If {channel} removes the post before it has been live for 24 hours, TON will be refunded.";
"lng_suggest_action_your_not_enough_stars" = "**Transaction failed** because you didn't have enough Stars.";
"lng_suggest_action_your_not_enough_ton" = "**Transaction failed** because you didn't have enough TON.";
"lng_suggest_action_his_not_enough_stars" = "**Transaction failed** because the user didn't have enough Stars.";
"lng_suggest_action_his_not_enough_ton" = "**Transaction failed** because the user didn't have enough TON.";
"lng_suggest_action_declined" = "{from} rejected the message.";
"lng_suggest_action_declined_reason" = "{from} rejected the message with the comment.";
"lng_suggest_change_price" = "{from} suggests a new price for the message.";
"lng_suggest_change_time" = "{from} suggests a new time for the message.";
"lng_suggest_change_price_time" = "{from} suggests a new price and time for the message.";
"lng_suggest_change_content" = "{from} suggests changes for the message.";
"lng_suggest_change_price_label" = "New Price";
"lng_suggest_change_time_label" = "New Time";
"lng_suggest_change_text_label" = "Check the suggested message below";
"lng_suggest_menu_edit_message" = "Edit Message";
"lng_suggest_menu_edit_price" = "Edit Price";
"lng_suggest_menu_edit_time" = "Edit Time";
"lng_suggest_decline_title" = "Decline";
"lng_suggest_decline_text" = "Do you want to decline publishing this post from {from}?";
"lng_suggest_decline_text_to" = "Do you want to decline publishing this post to {channel}?";
"lng_suggest_decline_reason" = "Add a reason (optional)";
"lng_suggest_accept_title" = "Accept Terms";
"lng_suggest_accept_text" = "Do you want to publish this post from {from}?";
"lng_suggest_accept_text_to" = "Do you want to publish this post to {channel}?";
"lng_suggest_accept_receive_stars#one" = "{channel} will receive **{count} Star** ({percent}) for publishing {date}.";
"lng_suggest_accept_receive_stars#other" = "{channel} will receive **{count} Stars** ({percent}) for publishing {date}.";
"lng_suggest_accept_receive_ton#one" = "{channel} will receive **{count} TON** ({percent}) for publishing {date}.";
"lng_suggest_accept_receive_ton#other" = "{channel} will receive **{count} TON** ({percent}) for publishing {date}.";
"lng_suggest_accept_receive_now_stars#one" = "{channel} will receive **{count} Star** ({percent}) for publishing right now.";
"lng_suggest_accept_receive_now_stars#other" = "{channel} will receive **{count} Stars** ({percent}) for publishing right now.";
"lng_suggest_accept_receive_now_ton#one" = "{channel} will receive **{count} TON** ({percent}) for publishing right now.";
"lng_suggest_accept_receive_now_ton#other" = "{channel} will receive **{count} TON** ({percent}) for publishing right now.";
"lng_suggest_accept_receive_if" = "It must remain visible for at least **24** hours after publication.";
"lng_suggest_accept_pay_stars#one" = "You will pay **{count} Star** for publishing {date}.";
"lng_suggest_accept_pay_stars#other" = "You will pay **{count} Stars** for publishing {date}.";
"lng_suggest_accept_pay_ton#one" = "You will pay **{count} TON** for publishing {date}.";
"lng_suggest_accept_pay_ton#other" = "You will pay **{count} TON** for publishing {date}.";
"lng_suggest_accept_pay_now_stars#one" = "You will pay **{count} Star** for publishing right now.";
"lng_suggest_accept_pay_now_stars#other" = "You will pay **{count} Stars** for publishing right now.";
"lng_suggest_accept_pay_now_ton#one" = "You will pay **{count} TON** for publishing right now.";
"lng_suggest_accept_pay_now_ton#other" = "You will pay **{count} TON** for publishing right now.";
"lng_suggest_accept_send" = "Publish";
"lng_suggest_stars_amount#one" = "{count} Star";
"lng_suggest_stars_amount#other" = "{count} Stars";
"lng_suggest_ton_amount#one" = "{count} TON";
"lng_suggest_ton_amount#other" = "{count} TON";
"lng_suggest_warn_title_stars" = "Stars will be lost";
"lng_suggest_warn_title_ton" = "TON will be lost";
"lng_suggest_warn_text_stars" = "You won't receive **Stars** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published.";
"lng_suggest_warn_text_ton" = "You won't receive **TON** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published.";
"lng_suggest_warn_delete_anyway" = "Delete Anyway";
"lng_suggest_low_ton_title" = "{amount} TON Needed";
"lng_suggest_low_ton_text" = "You can add funds to your balance via the third-party platform Fragment.";
"lng_suggest_low_ton_fragment" = "Add Funds via Fragment";
"lng_suggest_low_ton_fragment_url" = "https://fragment.com/ads/topup";
"lng_reply_in_another_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat";
@ -5130,6 +5326,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payment_bar_button" = "Remove Fee";
"lng_payment_refund_title" = "Remove Fee";
"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?";
"lng_payment_refund_channel" = "Do you want to allow {name} to message the channel for free?";
"lng_payment_refund_also#one" = "Refund already paid {count} Star";
"lng_payment_refund_also#other" = "Refund already paid {count} Stars";
"lng_payment_refund_confirm" = "Confirm";
@ -5159,6 +5356,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_channel_edit_stories" = "Edit stories of others";
"lng_rights_channel_delete_stories" = "Delete stories of others";
"lng_rights_channel_manage_calls" = "Manage live streams";
"lng_rights_channel_manage_direct" = "Manage direct messages";
"lng_rights_group_info" = "Change group info";
"lng_rights_group_ban" = "Ban users";
"lng_rights_group_invite_link" = "Invite users via link";
@ -5251,6 +5449,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_polls_all" = "Sorry, sending polls is not allowed in this group.";
"lng_restricted_send_public_polls" = "Sorry, polls with visible votes can't be forwarded to channels.";
"lng_restricted_send_todo_lists" = "Sorry, Checklists can't be forwarded to channels.";
"lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel.";
"lng_restricted_send_voice_messages" = "{user} doesn't accept voice messages.";
@ -5265,6 +5464,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
"lng_send_charges_stars_channel" = "{channel} charges {amount} per message to its admin.";
"lng_send_free_channel" = "Send a direct message to the administrator of {channel}.";
"lng_send_charges_stars_text" = "{user} charges {amount} for each message.";
"lng_send_charges_stars_go" = "Buy Stars";
@ -5480,6 +5681,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_admin_create_topics" = "Create topics";
"lng_admin_log_admin_manage_calls" = "Manage video chats";
"lng_admin_log_admin_manage_calls_channel" = "Manage live streams";
"lng_admin_log_admin_manage_direct" = "Manage direct messages";
"lng_admin_log_admin_add_admins" = "Add new admins";
"lng_admin_log_subscription_extend" = "{name} renewed subscription until {date}";
@ -5779,6 +5981,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_polls_stop" = "Stop poll";
"lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
"lng_polls_stop_sure" = "Stop";
"lng_polls_menu_item" = "Poll";
"lng_polls_create" = "Create poll";
"lng_polls_create_title" = "New poll";
"lng_polls_create_question" = "Question";
@ -5807,6 +6010,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_polls_show_more#other" = "Show more ({count})";
"lng_polls_votes_collapse" = "Collapse";
"lng_todo_title" = "Checklist";
"lng_todo_title_group" = "Group Checklist";
"lng_todo_completed#one" = "{count} of {total} completed";
"lng_todo_completed#other" = "{count} of {total} completed";
"lng_todo_completed_none" = "None of {total} completed";
"lng_todo_menu_item" = "Checklist";
"lng_todo_create" = "Create Checklist";
"lng_todo_create_title" = "New Checklist";
"lng_todo_create_title_placeholder" = "Title";
"lng_todo_create_list" = "Tasks List";
"lng_todo_create_list_add" = "Add a task...";
"lng_todo_create_limit#one" = "You can add {count} more task.";
"lng_todo_create_limit#other" = "You can add {count} more tasks.";
"lng_todo_create_maximum" = "You have added the maximum number of tasks.";
"lng_todo_create_settings" = "Settings";
"lng_todo_create_allow_add" = "Allow Others to Add Tasks";
"lng_todo_create_allow_mark" = "Allow Others to Mark As Done";
"lng_todo_create_button" = "Create";
"lng_todo_choose_title" = "Please enter a title.";
"lng_todo_choose_tasks" = "Please enter at least one task.";
"lng_todo_add_title" = "Add Tasks";
"lng_todo_create_premium" = "Only subscribers of {link} can create Checklists.";
"lng_todo_add_premium" = "Only subscribers of {link} can add tasks.";
"lng_todo_mark_premium" = "Only subscribers of {link} can mark tasks as done.";
"lng_todo_premium_link" = "Telegram Premium";
"lng_todo_mark_restricted" = "{user} has restricted others from marking tasks as done.";
"lng_todo_mark_forwarded" = "You can't change forwarded checklists.";
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";
"lng_outdated_title_bits" = "PLEASE SWITCH TO A 64-BIT OPERATING SYSTEM.";
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";
@ -6071,6 +6303,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forum_messages#other" = "{count} messages";
"lng_forum_show_topics_list" = "Show Topics List";
"lng_monoforum_choose_to_reply" = "Choose a message to reply.";
"lng_request_peer_requirements" = "Requirements";
"lng_request_peer_rights" = "You must have these admin rights: {rights}.";
"lng_request_peer_rights_and" = "{rights} and {last}";
@ -6117,6 +6351,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_request_channel_delete_messages" = "delete messages";
"lng_request_channel_add_subscribers" = "add subscribers";
"lng_request_channel_manage_livestreams" = "manage live streams";
"lng_request_channel_manage_direct" = "manage direct messages";
"lng_request_channel_add_admins" = "add new admins";
"lng_request_channel_create" = "Create a New Channel for This";
@ -6394,7 +6629,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_earn_balance_button_locked" = "Withdraw";
"lng_bot_earn_balance_button_buy_ads" = "Buy Ads";
"lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}";
"lng_self_earn_learn_credits_out_about" = "You can withdraw from 10 Stars using Fragment. {link}";
"lng_bot_earn_out_ph" = "Enter amount to withdraw";
"lng_bot_earn_out_ph_max" = "Enter amount to withdraw (max. {amount})";
"lng_bot_earn_balance_password_title" = "Two-step verification";
"lng_bot_earn_balance_password_description" = "Please enter your password to collect.";
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less than {link}.";
@ -6576,6 +6813,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mac_menu_new_channel" = "New Channel";
"lng_mac_menu_show" = "Show Telegram";
"lng_mac_menu_emoji_and_symbols" = "Emoji & Symbols";
"lng_mac_menu_fullscreen" = "Toggle Full Screen";
"lng_mac_menu_player_pause" = "Pause";
"lng_mac_menu_player_resume" = "Resume";
@ -6612,6 +6850,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_UseScheduledMessages" = "Schedule Messages";
"ayu_UseScheduledMessagesDescription" = "Automatically schedules outgoing messages to send after ~12 seconds (longer for media). Using this feature, you won't appear online.\nAvoid using on unreliable networks.";
"ayu_SendWithoutSoundByDefault" = "Send without Sound";
"ayu_SendWithoutSoundByDefaultNever" = "Never";
"ayu_SendWithoutSoundByDefaultInGhostMode" = "When using Ghost Mode";
"ayu_SendWithoutSoundByDefaultAlways" = "Always";
"ayu_SendWithoutSoundByDefaultDescription" = "Sends outgoing messages without sound by default.";
"ayu_SuggestGhostModeBeforeViewingStory" = "Story Ghost Mode Alert";
"ayu_SuggestGhostModeBeforeViewingStoryDescription" = "Displays an alert before opening a story, suggesting you enable Ghost Mode.";
@ -6626,6 +6867,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_MessageSavingSaveMediaInPublicGroups" = "Public Groups";
"ayu_MessageSavingSaveMediaInPrivateGroups" = "Private Groups";
"ayu_MessageSavingSaveMediaHintPopup" = "Make sure it's configured to your needs.";
"ayu_AttachmentsFolderMaxSizeHeader" = "Max folder size";
"ayu_AttachmentsFolderMaxSizeDescription" = "If folder size exceeds this limit, the oldest attachments will be removed from the device.";
"ayu_MaximumMediaSizeCellular" = "Media size limit (cellular data)";
"ayu_MaximumMediaSizeWiFi" = "Media size limit (WiFi)";
"ayu_MessageSavingOtherHeader" = "Other";
@ -6647,6 +6890,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_LocalPremium" = "Local Telegram Premium";
"ayu_DisplayGhostStatus" = "Display Ghost Mode Status";
"ayu_CopyUsernameAsLink" = "Copy Username as Link";
"ayu_HideChannelReactions" = "Hide Reactions in Channels";
"ayu_HideGroupReactions" = "Hide Reactions in Groups";
"ayu_CustomizationHeader" = "Customization";
"ayu_DeletedMarkText" = "Deleted Mark";
"ayu_DeletedMarkNothing" = "Nothing";
@ -6710,6 +6955,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_RegexFiltersExcludedAmount#other" = "{count} excluded";
"ayu_RegexFiltersHeader" = "Filters";
"ayu_RegexFiltersShared" = "Shared Filters";
"ayu_FiltersShadowBan" = "Shadow Ban";
"ayu_RegexFiltersExcluded" = "Excluded Filters";
"ayu_RegexFiltersEnable" = "Enable Filters";
"ayu_FiltersHideFromBlocked" = "Hide from Blocked Users";
@ -6726,6 +6972,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_RegexFilterBulletinText" = "Filter added to the **Shared filters**.";
"ayu_RegexFilterBulletinAction" = "To Current Chat";
"ayu_RegexFiltersListEmpty" = "No filters here yet.";
"ayu_FiltersQuickShadowBan" = "Shadow Ban";
"ayu_FiltersQuickUnshadowBan" = "Unshadow Ban";
"ayu_FiltersMenuSelectChat" = "Select Chat";
"ayu_FiltersMenuImport" = "Import";
"ayu_FiltersMenuExport" = "Export";
@ -6767,6 +7015,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_IconNothing" = "Nothing";
"ayu_IconBard" = "Google Bard";
"ayu_IconYaPlus" = "Yandex Plus";
"ayu_IconAyuTyan" = "AyuTyan";
"ayu_WALMode" = "Enable WAL mode";
"ayu_PushNotificationCount" = "FCM notifications received";
"ayu_AyuAttachments" = "AyuGram Attachments Folder";
@ -6835,6 +7084,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_ClearDeletedMessagesTitle" = "Clear deleted messages";
"ayu_ClearDeletedMessagesText" = "Are you sure you want to clear all deleted messages in this chat?";
"ayu_ClearDeletedMessagesActionText" = "Clear";
"ayu_DeleteOwnMessages" = "Delete own messages";
"ayu_DeleteOwnMessagesConfirmation" = "Are you sure you want to delete all your messages from this group?";
"ayu_SuggestGhostModeTitle" = "Ghost Mode";
"ayu_SuggestGhostModeStoryText" = "Do you want to enable **Ghost Mode** before viewing the story?";
"ayu_SuggestGhostModeStoryActionTextYes" = "Yes";

View file

@ -9,6 +9,7 @@
<file alias="cloud_password/password_input.tgs">../../animations/cloud_password/password_input.tgs</file>
<file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file>
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
<file alias="cloud_password/validate.tgs">../../animations/cloud_password/validate.tgs</file>
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
<file alias="stats.tgs">../../animations/stats.tgs</file>
@ -25,6 +26,7 @@
<file alias="hours.tgs">../../animations/hours.tgs</file>
<file alias="phone.tgs">../../animations/phone.tgs</file>
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
<file alias="diamond.tgs">../../animations/diamond.tgs</file>
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
<file alias="search.tgs">../../animations/search.tgs</file>
@ -32,6 +34,11 @@
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="starref_link.tgs">../../animations/starref_link.tgs</file>
<file alias="media_forbidden.tgs">../../animations/media_forbidden.tgs</file>
<file alias="topics.tgs">../../animations/edit_peers/topics.tgs</file>
<file alias="topics_tabs.tgs">../../animations/edit_peers/topics_tabs.tgs</file>
<file alias="topics_list.tgs">../../animations/edit_peers/topics_list.tgs</file>
<file alias="direct_messages.tgs">../../animations/edit_peers/direct_messages.tgs</file>
<file alias="no_chats.tgs">../../animations/no_chats.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.14.3.0" />
Version="5.16.4.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,14,3,0
PRODUCTVERSION 5,14,3,0
FILEVERSION 5,16,4,0
PRODUCTVERSION 5,16,4,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.14.3.0"
VALUE "FileVersion", "5.16.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.14.3.0"
VALUE "ProductVersion", "5.16.4.0"
END
END
BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,14,3,0
PRODUCTVERSION 5,14,3,0
FILEVERSION 5,16,4,0
PRODUCTVERSION 5,16,4,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.14.3.0"
VALUE "FileVersion", "5.16.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.14.3.0"
VALUE "ProductVersion", "5.16.4.0"
END
END
BLOCK "VarFileInfo"

View file

@ -282,7 +282,7 @@ int main(int argc, char *argv[])
cout << "Compression start, size: " << resultSize << "\n";
QByteArray compressed, resultCheck;
#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win
#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size

View file

@ -27,7 +27,7 @@ extern "C" {
#include <openssl/evp.h>
} // extern "C"
#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win
#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win
#include <LzmaLib.h>
#else
#include <lzma.h>

View file

@ -5,6 +5,7 @@ the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#define _GLIBCXX_USE_CXX11_ABI 0
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_cloud_password.h"
#include "api/api_send_progress.h"
#include "api/api_suggest_post.h"
#include "boxes/share_box.h"
#include "boxes/passcode_box.h"
#include "boxes/url_auth_box.h"
@ -399,10 +400,12 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
}
}
const auto replyTo = FullReplyTo();
const auto suggest = SuggestPostOptions();
Window::PeerMenuCreatePoll(
controller,
item->history()->peer,
replyTo,
suggest,
chosen,
disabled);
} break;
@ -519,6 +522,27 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
controller->showToast(tr::lng_text_copied(tr::now));
}
} break;
case ButtonType::SuggestAccept: {
Api::AcceptClickHandler(item)->onClick(ClickContext{
Qt::LeftButton,
QVariant::fromValue(context),
});
} break;
case ButtonType::SuggestDecline: {
Api::DeclineClickHandler(item)->onClick(ClickContext{
Qt::LeftButton,
QVariant::fromValue(context),
});
} break;
case ButtonType::SuggestChange: {
Api::SuggestChangesClickHandler(item)->onClick(ClickContext{
Qt::LeftButton,
QVariant::fromValue(context),
});
} break;
}
}

View file

@ -256,6 +256,7 @@ void ConfirmSubscriptionBox(
{
const auto balance = Settings::AddBalanceWidget(
content,
session,
session->credits().balanceValue(),
true);
session->credits().load(true);
@ -436,6 +437,12 @@ void CheckChatInvite(
}
});
}, [=](const MTP::Error &error) {
if (MTP::IsFloodError(error)) {
if (const auto strong = weak.get()) {
strong->show(Ui::MakeInformBox(tr::lng_flood_error()));
}
return;
}
if (error.code() != 400) {
return;
}

View file

@ -14,7 +14,7 @@ class ChannelData;
namespace Info::Profile {
class Badge;
enum class BadgeType;
enum class BadgeType : uchar;
} // namespace Info::Profile
namespace Main {

View file

@ -494,8 +494,15 @@ void ChatParticipants::requestBots(not_null<ChannelData*> channel) {
LOG(("API Error: "
"channels.channelParticipantsNotModified received!"));
});
}).fail([=] {
}).fail([=](const MTP::Error &error) {
_botsRequests.remove(channel);
if (error.type() == u"CHANNEL_MONOFORUM_UNSUPPORTED"_q) {
channel->mgInfo->bots.clear();
channel->mgInfo->botStatus = -1;
channel->session().changes().peerUpdated(
channel,
Data::PeerUpdate::Flag::FullInfo);
}
}).send();
_botsRequests[channel] = requestId;
@ -648,10 +655,7 @@ void ChatParticipants::Restrict(
channel->session().api().request(MTPchannels_EditBanned(
channel->inputChannel,
participant->input,
MTP_chatBannedRights(
MTP_flags(MTPDchatBannedRights::Flags::from_raw(
uint32(newRights.flags))),
MTP_int(newRights.until))
RestrictionsToMTP(newRights)
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);
channel->applyEditBanned(participant, oldRights, newRights);
@ -756,10 +760,7 @@ void ChatParticipants::kick(
const auto requestId = _api.request(MTPchannels_EditBanned(
channel->inputChannel,
participant->input,
MTP_chatBannedRights(
MTP_flags(
MTPDchatBannedRights::Flags::from_raw(uint32(rights.flags))),
MTP_int(rights.until))
RestrictionsToMTP(rights)
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);

View file

@ -14,6 +14,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
MTPSuggestedPost SuggestToMTP(SuggestPostOptions suggest) {
using Flag = MTPDsuggestedPost::Flag;
return suggest.exists
? MTP_suggestedPost(
MTP_flags((suggest.date ? Flag::f_schedule_date : Flag())
| (suggest.price().empty() ? Flag() : Flag::f_price)),
StarsAmountToTL(suggest.price()),
MTP_int(suggest.date))
: MTPSuggestedPost();
}
SendAction::SendAction(
not_null<Data::Thread*> thread,
SendOptions options)

View file

@ -19,6 +19,8 @@ namespace Api {
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
[[nodiscard]] MTPSuggestedPost SuggestToMTP(SuggestPostOptions suggest);
struct SendOptions {
uint64 price = 0;
PeerData *sendAs = nullptr;
@ -31,6 +33,7 @@ struct SendOptions {
bool invertCaption = false;
bool hideViaBot = false;
crl::time ttlSeconds = 0;
SuggestPostOptions suggest;
friend inline bool operator==(
const SendOptions &,

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_credits.h"
#include "api/api_credits_history_entry.h"
#include "api/api_premium.h"
#include "api/api_statistics_data_deserialize.h"
#include "api/api_updates.h"
@ -27,146 +28,6 @@ namespace {
constexpr auto kTransactionsLimit = 100;
[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
const MTPStarsTransaction &tl,
not_null<PeerData*> peer) {
using HistoryPeerTL = MTPDstarsTransactionPeer;
using namespace Data;
const auto owner = &peer->owner();
const auto photo = tl.data().vphoto()
? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
: nullptr;
auto extended = std::vector<CreditsHistoryMedia>();
if (const auto list = tl.data().vextended_media()) {
extended.reserve(list->v.size());
for (const auto &media : list->v) {
media.match([&](const MTPDmessageMediaPhoto &data) {
if (const auto inner = data.vphoto()) {
const auto photo = owner->processPhoto(*inner);
if (!photo->isNull()) {
extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryMediaType::Photo,
.id = photo->id,
});
}
}
}, [&](const MTPDmessageMediaDocument &data) {
if (const auto inner = data.vdocument()) {
const auto document = owner->processDocument(
*inner,
data.valt_documents());
if (document->isAnimation()
|| document->isVideoFile()
|| document->isGifv()) {
extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryMediaType::Video,
.id = document->id,
});
}
}
}, [&](const auto &) {});
}
}
const auto barePeerId = tl.data().vpeer().match([](
const HistoryPeerTL &p) {
return peerFromMTP(p.vpeer());
}, [](const auto &) {
return PeerId(0);
}).value;
const auto stargift = tl.data().vstargift();
const auto nonUniqueGift = stargift
? stargift->match([&](const MTPDstarGift &data) {
return &data;
}, [](const auto &) { return (const MTPDstarGift*)nullptr; })
: nullptr;
const auto reaction = tl.data().is_reaction();
const auto amount = Data::FromTL(tl.data().vstars());
const auto starrefAmount = tl.data().vstarref_amount()
? Data::FromTL(*tl.data().vstarref_amount())
: StarsAmount();
const auto starrefCommission
= tl.data().vstarref_commission_permille().value_or_empty();
const auto starrefBarePeerId = tl.data().vstarref_peer()
? peerFromMTP(*tl.data().vstarref_peer()).value
: 0;
const auto incoming = (amount >= StarsAmount());
const auto paidMessagesCount
= tl.data().vpaid_messages().value_or_empty();
const auto premiumMonthsForStars
= tl.data().vpremium_gift_months().value_or_empty();
const auto saveActorId = (reaction
|| !extended.empty()
|| paidMessagesCount) && incoming;
const auto parsedGift = stargift
? FromTL(&peer->session(), *stargift)
: std::optional<Data::StarGift>();
const auto giftStickerId = parsedGift ? parsedGift->document->id : 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()) },
.date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),
.credits = Data::FromTL(tl.data().vstars()),
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64(
tl.data().vgiveaway_post_id().value_or_empty()),
.bareGiftStickerId = giftStickerId,
.bareActorId = saveActorId ? barePeerId : uint64(0),
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
.starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
.starrefCommission = paidMessagesCount ? 0 : starrefCommission,
.starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
return Data::CreditsHistoryEntry::PeerType::PlayMarket;
}, [](const MTPDstarsTransactionPeerFragment &) {
return Data::CreditsHistoryEntry::PeerType::Fragment;
}, [](const MTPDstarsTransactionPeerAppStore &) {
return Data::CreditsHistoryEntry::PeerType::AppStore;
}, [](const MTPDstarsTransactionPeerUnsupported &) {
return Data::CreditsHistoryEntry::PeerType::Unsupported;
}, [](const MTPDstarsTransactionPeerPremiumBot &) {
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
}, [](const MTPDstarsTransactionPeerAds &) {
return Data::CreditsHistoryEntry::PeerType::Ads;
}, [](const MTPDstarsTransactionPeerAPI &) {
return Data::CreditsHistoryEntry::PeerType::API;
}),
.subscriptionUntil = tl.data().vsubscription_period()
? base::unixtime::parse(base::unixtime::now()
+ tl.data().vsubscription_period()->v)
: QDateTime(),
.successDate = tl.data().vtransaction_date()
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.paidMessagesCount = paidMessagesCount,
.paidMessagesAmount = (paidMessagesCount
? starrefAmount
: StarsAmount()),
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
.starsConverted = int(nonUniqueGift
? nonUniqueGift->vconvert_stars().v
: 0),
.premiumMonthsForStars = premiumMonthsForStars,
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming,
.stargift = stargift.has_value(),
.giftUpgraded = tl.data().is_stargift_upgrade(),
.giftResale = tl.data().is_stargift_resale(),
.reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
.failed = tl.data().is_failed(),
.in = incoming,
.gift = tl.data().is_gift() || stargift.has_value(),
};
}
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
const MTPStarsSubscription &tl,
not_null<PeerData*> peer) {
@ -203,7 +64,7 @@ constexpr auto kTransactionsLimit = 100;
if (const auto history = data.vhistory()) {
entries.reserve(history->v.size());
for (const auto &tl : history->v) {
entries.push_back(HistoryFromTL(tl, peer));
entries.push_back(CreditsHistoryEntryFromTL(tl, peer));
}
}
auto subscriptions = std::vector<Data::SubscriptionEntry>();
@ -216,7 +77,7 @@ constexpr auto kTransactionsLimit = 100;
return Data::CreditsStatusSlice{
.list = std::move(entries),
.subscriptions = std::move(subscriptions),
.balance = Data::FromTL(status.data().vbalance()),
.balance = CreditsAmountFromTL(status.data().vbalance()),
.subscriptionsMissingBalance
= status.data().vsubscriptions_missing_balance().value_or_empty(),
.allLoaded = !status.data().vnext_offset().has_value()
@ -300,11 +161,14 @@ void CreditsStatus::request(
using TLResult = MTPpayments_StarsStatus;
_requestId = _api.request(MTPpayments_GetStarsStatus(
MTP_flags(0),
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
)).done([=](const TLResult &result) {
_requestId = 0;
const auto &balance = result.data().vbalance();
_peer->session().credits().apply(_peer->id, Data::FromTL(balance));
_peer->session().credits().apply(
_peer->id,
CreditsAmountFromTL(balance));
if (const auto onstack = done) {
onstack(StatusFromTL(result, _peer));
}
@ -316,13 +180,18 @@ void CreditsStatus::request(
}).send();
}
CreditsHistory::CreditsHistory(not_null<PeerData*> peer, bool in, bool out)
CreditsHistory::CreditsHistory(
not_null<PeerData*> peer,
bool in,
bool out,
bool currency)
: _peer(peer)
, _flags((in == out)
, _flags(((in == out)
? HistoryTL::Flags(0)
: HistoryTL::Flags(0)
| (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0))
| (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0)))
| (currency ? HistoryTL::Flag::f_ton : HistoryTL::Flags(0)))
, _api(&peer->session().api().instance()) {
}
@ -414,19 +283,21 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
auto lifetime = rpl::lifetime();
const auto finish = [=](const QString &url) {
makeRequest(MTPpayments_GetStarsRevenueStats(
api().request(MTPpayments_GetStarsRevenueStats(
MTP_flags(0),
(_isUser ? user()->input : channel()->input)
)).done([=](const MTPpayments_StarsRevenueStats &result) {
const auto &data = result.data();
const auto &status = data.vstatus().data();
using Data::FromTL;
_data = Data::CreditsEarnStatistics{
.revenueGraph = StatisticalGraphFromTL(
data.vrevenue_graph()),
.currentBalance = FromTL(status.vcurrent_balance()),
.availableBalance = FromTL(status.vavailable_balance()),
.overallRevenue = FromTL(status.voverall_revenue()),
.currentBalance = CreditsAmountFromTL(
status.vcurrent_balance()),
.availableBalance = CreditsAmountFromTL(
status.vavailable_balance()),
.overallRevenue = CreditsAmountFromTL(
status.voverall_revenue()),
.usdRate = data.vusd_rate().v,
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
.nextWithdrawalAt = status.vnext_withdrawal_at()
@ -442,7 +313,7 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
}).send();
};
makeRequest(
api().request(
MTPpayments_GetStarsRevenueAdsAccountUrl(
(_isUser ? user()->input : channel()->input))
).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {

View file

@ -75,7 +75,11 @@ private:
class CreditsHistory final {
public:
CreditsHistory(not_null<PeerData*> peer, bool in, bool out);
CreditsHistory(
not_null<PeerData*> peer,
bool in,
bool out,
bool currency = false);
void request(
const Data::CreditsStatusSlice::OffsetToken &token,

View file

@ -0,0 +1,167 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_credits_history_entry.h"
#include "api/api_premium.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "data/data_credits.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
namespace Api {
Data::CreditsHistoryEntry CreditsHistoryEntryFromTL(
const MTPStarsTransaction &tl,
not_null<PeerData*> peer) {
using HistoryPeerTL = MTPDstarsTransactionPeer;
using namespace Data;
const auto owner = &peer->owner();
const auto photo = tl.data().vphoto()
? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
: nullptr;
auto extended = std::vector<CreditsHistoryMedia>();
if (const auto list = tl.data().vextended_media()) {
extended.reserve(list->v.size());
for (const auto &media : list->v) {
media.match([&](const MTPDmessageMediaPhoto &data) {
if (const auto inner = data.vphoto()) {
const auto photo = owner->processPhoto(*inner);
if (!photo->isNull()) {
extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryMediaType::Photo,
.id = photo->id,
});
}
}
}, [&](const MTPDmessageMediaDocument &data) {
if (const auto inner = data.vdocument()) {
const auto document = owner->processDocument(
*inner,
data.valt_documents());
if (document->isAnimation()
|| document->isVideoFile()
|| document->isGifv()) {
extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryMediaType::Video,
.id = document->id,
});
}
}
}, [&](const auto &) {});
}
}
const auto barePeerId = tl.data().vpeer().match([](
const HistoryPeerTL &p) {
return peerFromMTP(p.vpeer());
}, [](const auto &) {
return PeerId(0);
}).value;
const auto stargift = tl.data().vstargift();
const auto nonUniqueGift = stargift
? stargift->match([&](const MTPDstarGift &data) {
return &data;
}, [](const auto &) { return (const MTPDstarGift*)nullptr; })
: nullptr;
const auto reaction = tl.data().is_reaction();
const auto amount = CreditsAmountFromTL(tl.data().vamount());
const auto starrefAmount = CreditsAmountFromTL(
tl.data().vstarref_amount());
const auto starrefCommission
= tl.data().vstarref_commission_permille().value_or_empty();
const auto starrefBarePeerId = tl.data().vstarref_peer()
? peerFromMTP(*tl.data().vstarref_peer()).value
: 0;
const auto incoming = (amount >= CreditsAmount());
const auto paidMessagesCount
= tl.data().vpaid_messages().value_or_empty();
const auto premiumMonthsForStars
= tl.data().vpremium_gift_months().value_or_empty();
const auto saveActorId = (reaction
|| !extended.empty()
|| paidMessagesCount) && incoming;
const auto parsedGift = stargift
? FromTL(&peer->session(), *stargift)
: std::optional<Data::StarGift>();
const auto giftStickerId = parsedGift ? parsedGift->document->id : 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()) },
.date = base::unixtime::parse(
tl.data().vads_proceeds_from_date().value_or(
tl.data().vdate().v)),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),
.credits = CreditsAmountFromTL(tl.data().vamount()),
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64(
tl.data().vgiveaway_post_id().value_or_empty()),
.bareGiftStickerId = giftStickerId,
.bareActorId = saveActorId ? barePeerId : uint64(0),
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
.starrefAmount = paidMessagesCount ? CreditsAmount() : starrefAmount,
.starrefCommission = paidMessagesCount ? 0 : starrefCommission,
.starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
return Data::CreditsHistoryEntry::PeerType::PlayMarket;
}, [](const MTPDstarsTransactionPeerFragment &) {
return Data::CreditsHistoryEntry::PeerType::Fragment;
}, [](const MTPDstarsTransactionPeerAppStore &) {
return Data::CreditsHistoryEntry::PeerType::AppStore;
}, [](const MTPDstarsTransactionPeerUnsupported &) {
return Data::CreditsHistoryEntry::PeerType::Unsupported;
}, [](const MTPDstarsTransactionPeerPremiumBot &) {
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
}, [](const MTPDstarsTransactionPeerAds &) {
return Data::CreditsHistoryEntry::PeerType::Ads;
}, [](const MTPDstarsTransactionPeerAPI &) {
return Data::CreditsHistoryEntry::PeerType::API;
}),
.subscriptionUntil = tl.data().vsubscription_period()
? base::unixtime::parse(base::unixtime::now()
+ tl.data().vsubscription_period()->v)
: QDateTime(),
.adsProceedsToDate = tl.data().vads_proceeds_to_date()
? base::unixtime::parse(tl.data().vads_proceeds_to_date()->v)
: QDateTime(),
.successDate = tl.data().vtransaction_date()
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.paidMessagesCount = paidMessagesCount,
.paidMessagesAmount = (paidMessagesCount
? starrefAmount
: CreditsAmount()),
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
.starsConverted = int(nonUniqueGift
? nonUniqueGift->vconvert_stars().v
: 0),
.premiumMonthsForStars = premiumMonthsForStars,
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming,
.stargift = stargift.has_value(),
.giftUpgraded = tl.data().is_stargift_upgrade(),
.giftResale = tl.data().is_stargift_resale(),
.reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
.failed = tl.data().is_failed(),
.in = incoming,
.gift = tl.data().is_gift() || stargift.has_value(),
};
}
} // namespace Api

View file

@ -0,0 +1,22 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class PeerData;
namespace Data {
struct CreditsHistoryEntry;
} // namespace Data
namespace Api {
[[nodiscard]] Data::CreditsHistoryEntry CreditsHistoryEntryFromTL(
const MTPStarsTransaction &tl,
not_null<PeerData*> peer);
} // namespace Api

View file

@ -56,7 +56,6 @@ void HandleWithdrawalButton(
? &currencyReceiver->session()
: &creditsReceiver->session());
using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
session->api().cloudPassword().reload();
@ -98,19 +97,19 @@ void HandleWithdrawalButton(
show->showToast(message);
}
};
if (currencyReceiver) {
session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl(
currencyReceiver->input,
result.result
)).done([=](const ChannelOutUrl &r) {
done(qs(r.data().vurl()));
}).fail(fail).send();
} else if (creditsReceiver) {
if (currencyReceiver || creditsReceiver) {
using F = MTPpayments_getStarsRevenueWithdrawalUrl::Flag;
session->api().request(
MTPpayments_GetStarsRevenueWithdrawalUrl(
creditsReceiver->input,
MTP_long(receiver.creditsAmount()),
MTP_flags(currencyReceiver
? F::f_ton
: F::f_amount),
currencyReceiver
? currencyReceiver->input
: creditsReceiver->input,
MTP_long(creditsReceiver
? receiver.creditsAmount()
: 0),
result.result
)).done([=](const CreditsOutUrl &r) {
done(qs(r.data().vurl()));
@ -138,17 +137,19 @@ void HandleWithdrawalButton(
processOut();
}
};
if (currencyReceiver) {
session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl(
currencyReceiver->input,
MTP_inputCheckPasswordEmpty()
)).fail(fail).send();
} else if (creditsReceiver) {
if (currencyReceiver || creditsReceiver) {
using F = MTPpayments_getStarsRevenueWithdrawalUrl::Flag;
session->api().request(
MTPpayments_GetStarsRevenueWithdrawalUrl(
creditsReceiver->input,
MTP_long(receiver.creditsAmount()),
MTP_flags(currencyReceiver
? F::f_ton
: F::f_amount),
currencyReceiver
? currencyReceiver->input
: creditsReceiver->input,
MTP_long(creditsReceiver
? receiver.creditsAmount()
: 0),
MTP_inputCheckPasswordEmpty()
)).fail(fail).send();
}

View file

@ -10,15 +10,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_media.h"
#include "api/api_text_entities.h"
#include "base/random.h"
#include "ui/boxes/confirm_box.h"
#include "data/business/data_shortcut_messages.h"
#include "data/components/scheduled_messages.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_todo_list.h"
#include "data/data_web_page.h"
#include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mtproto/mtproto_response.h"
@ -45,6 +49,193 @@ template <typename T>
constexpr auto ErrorWithoutId
= is_callable_plain_v<T, QString>;
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMessage(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail) {
Expects(options.suggest.exists);
Expects(!options.scheduled);
const auto session = &item->history()->session();
const auto api = &session->api();
const auto thread = item->history()->amMonoforumAdmin()
? item->savedSublist()
: (Data::Thread*)item->history();
auto action = SendAction(thread, options);
action.replyTo = FullReplyTo{
.messageId = item->fullId(),
.monoforumPeerId = (item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId()),
};
auto message = MessageToSend(std::move(action));
message.textWithTags = TextWithTags{
textWithEntities.text,
TextUtilities::ConvertEntitiesToTextTags(textWithEntities.entities)
};
message.webPage = webpage;
api->sendMessage(std::move(message));
const auto requestId = -1;
crl::on_main(session, [=] {
const auto type = u"MESSAGE_NOT_MODIFIED"_q;
if constexpr (ErrorWithId<FailCallback>) {
fail(type, requestId);
} else if constexpr (ErrorWithoutId<FailCallback>) {
fail(type);
} else if constexpr (WithoutCallback<FailCallback>) {
fail();
} else {
t_bad_callback(fail);
}
});
return requestId;
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMedia(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia) {
Expects(options.suggest.exists);
Expects(!options.scheduled);
const auto session = &item->history()->session();
const auto api = &session->api();
const auto text = textWithEntities.text;
const auto sentEntities = EntitiesToMTP(
session,
textWithEntities.entities,
ConvertOption::SkipLocal);
const auto updateRecentStickers = inputMedia
? Api::HasAttachedStickers(*inputMedia)
: false;
const auto emptyFlag = MTPmessages_SendMedia::Flag(0);
auto replyTo = FullReplyTo{
.messageId = item->fullId(),
.monoforumPeerId = (item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId()),
};
const auto flags = emptyFlag
| MTPmessages_SendMedia::Flag::f_reply_to
| MTPmessages_SendMedia::Flag::f_suggested_post
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_SendMedia::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_SendMedia::Flag::f_entities
: emptyFlag)
| (options.starsApproved
? MTPmessages_SendMedia::Flag::f_allow_paid_stars
: emptyFlag);
const auto randomId = base::RandomValue<uint64>();
return api->request(MTPmessages_SendMedia(
MTP_flags(flags),
item->history()->peer->input,
ReplyToForMTP(item->history(), replyTo),
inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
MTP_string(text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTPint(), // schedule_date
MTPInputPeer(), // send_as
MTPInputQuickReplyShortcut(), // quick_reply_shortcut
MTPlong(), // effect
MTP_long(options.starsApproved),
Api::SuggestToMTP(options.suggest)
)).done([=](
const MTPUpdates &result,
[[maybe_unused]] mtpRequestId requestId) {
const auto apply = [=] { api->applyUpdates(result); };
if constexpr (WithId<DoneCallback>) {
done(apply, requestId);
} else if constexpr (WithoutId<DoneCallback>) {
done(apply);
} else if constexpr (WithoutCallback<DoneCallback>) {
done();
apply();
} else {
t_bad_callback(done);
}
if (updateRecentStickers) {
api->requestSpecialStickersForce(false, false, true);
}
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
if constexpr (ErrorWithId<FailCallback>) {
fail(error.type(), requestId);
} else if constexpr (ErrorWithoutId<FailCallback>) {
fail(error.type());
} else if constexpr (WithoutCallback<FailCallback>) {
fail();
} else {
t_bad_callback(fail);
}
}).send();
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMessageOrMedia(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia) {
const auto wasMedia = item->media();
if (!inputMedia && wasMedia && wasMedia->allowsEditCaption()) {
if (const auto photo = wasMedia->photo()) {
inputMedia = MTP_inputMediaPhoto(
MTP_flags(0),
photo->mtpInput(),
MTPint()); // ttl_seconds
} else if (const auto document = wasMedia->document()) {
inputMedia = MTP_inputMediaDocument(
MTP_flags(0),
document->mtpInput(),
MTPInputPhoto(), // video_cover
MTPint(), // video_timestamp
MTPint(), // ttl_seconds
MTPstring()); // query
}
}
if (inputMedia) {
return SuggestMedia(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail),
inputMedia);
}
return SuggestMessage(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail));
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId EditMessage(
not_null<HistoryItem*> item,
@ -54,6 +245,18 @@ mtpRequestId EditMessage(
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
if (item->computeSuggestionActions()
== SuggestionActions::AcceptAndDecline) {
return SuggestMessageOrMedia(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail),
inputMedia);
}
const auto session = &item->history()->session();
const auto api = &session->api();
@ -70,31 +273,31 @@ mtpRequestId EditMessage(
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
const auto flags = emptyFlag
| ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
| (options.scheduled
? MTPmessages_EditMessage::Flag::f_schedule_date
: emptyFlag)
| (item->isBusinessShortcut()
? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
: emptyFlag);
| ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
| (options.scheduled
? MTPmessages_EditMessage::Flag::f_schedule_date
: emptyFlag)
| (item->isBusinessShortcut()
? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
: emptyFlag);
const auto id = item->isScheduled()
? session->scheduledMessages().lookupId(item)
@ -358,4 +561,22 @@ mtpRequestId EditTextMessage(
std::nullopt);
}
void EditTodoList(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail) {
const auto callback = [=](Fn<void()> applyUpdates, mtpRequestId id) {
applyUpdates();
done(id);
};
EditMessage(
item,
options,
callback,
fail,
MTP_inputMediaTodo(TodoListDataToMTP(&data)));
}
} // namespace Api

View file

@ -58,4 +58,11 @@ mtpRequestId EditTextMessage(
Fn<void(const QString &error, mtpRequestId requestId)> fail,
bool spoilered);
void EditTodoList(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail);
} // namespace Api

View file

@ -52,6 +52,7 @@ void Polls::create(
const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId
: 0;
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
@ -59,9 +60,9 @@ void Polls::create(
const auto clearCloudDraft = action.clearDraft;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearLocalDraft(topicRootId);
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
history->clearLocalDraft(topicRootId, monoforumPeerId);
history->clearCloudDraft(topicRootId, monoforumPeerId);
history->startSavingCloudDraft(topicRootId, monoforumPeerId);
}
const auto silentPost = ShouldSendSilent(peer, action.options);
const auto starsPaid = std::min(
@ -79,6 +80,9 @@ void Polls::create(
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.suggest) {
sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
@ -106,11 +110,13 @@ void Polls::create(
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTP_long(action.options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
SuggestToMTP(action.options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
}
_session->changes().historyUpdated(
@ -123,6 +129,7 @@ void Polls::create(
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
}
fail();
@ -171,7 +178,7 @@ void Polls::sendVotes(
hideSending();
_session->updates().applyUpdates(result);
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!settings.sendReadMessages && settings.markReadAfterAction && item)
{
readHistory(item);

View file

@ -424,7 +424,7 @@ void Premium::requestPremiumRequiredSlice() {
constexpr auto hasPrem = Flag::HasRequirePremiumToWrite;
constexpr auto hasStars = Flag::HasStarsPerMessage;
user->setStarsPerMessage(stars);
user->setFlags((user->flags() & ~(me | hasPrem | hasStars))
user->setFlags((user->flags() & ~me)
| known
| (requirePremium ? (me | hasPrem) : Flag())
| (stars ? hasStars : Flag()));
@ -619,6 +619,8 @@ auto PremiumGiftCodeOptions::requestStarGifts()
MTP_int(0)
)).done([=](const MTPpayments_StarGifts &result) {
result.match([&](const MTPDpayments_starGifts &data) {
_peer->owner().processUsers(data.vusers());
_peer->owner().processChats(data.vchats());
_giftsHash = data.vhash().v;
const auto &list = data.vgifts().v;
const auto session = &_peer->session();
@ -805,6 +807,12 @@ std::optional<Data::StarGift> FromTL(
if (!document->sticker()) {
return std::optional<Data::StarGift>();
}
const auto releasedById = data.vreleased_by()
? peerFromMTP(*data.vreleased_by())
: PeerId();
const auto releasedBy = releasedById
? session->data().peer(releasedById).get()
: nullptr;
return std::optional<Data::StarGift>(Data::StarGift{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
@ -812,6 +820,7 @@ std::optional<Data::StarGift> FromTL(
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
.starsResellMin = int64(resellPrice),
.document = document,
.releasedBy = releasedBy,
.resellTitle = qs(data.vtitle().value_or_empty()),
.resellCount = int(data.vavailability_resale().value_or_empty()),
.limitedLeft = remaining.value_or_empty(),
@ -841,6 +850,12 @@ std::optional<Data::StarGift> FromTL(
|| !pattern->document->sticker()) {
return std::optional<Data::StarGift>();
}
const auto releasedById = data.vreleased_by()
? peerFromMTP(*data.vreleased_by())
: PeerId();
const auto releasedBy = releasedById
? session->data().peer(releasedById).get()
: nullptr;
auto result = Data::StarGift{
.id = uint64(data.vid().v),
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
@ -852,12 +867,14 @@ std::optional<Data::StarGift> FromTL(
.ownerId = (data.vowner_id()
? peerFromMTP(*data.vowner_id())
: PeerId()),
.releasedBy = releasedBy,
.number = data.vnum().v,
.starsForResale = int(data.vresell_stars().value_or_empty()),
.model = *model,
.pattern = *pattern,
}),
.document = model->document,
.releasedBy = releasedBy,
.limitedLeft = (total - data.vavailability_issued().v),
.limitedCount = total,
};

View file

@ -118,7 +118,7 @@ void SendProgressManager::send(const Key &key, int progress) {
}
// AyuGram sendUploadProgress
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!settings.sendUploadProgress)
{
DEBUG_LOG(("[AyuGram] Don't send upload progress"));

View file

@ -109,6 +109,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.suggest) {
sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post;
}
if (action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
@ -136,7 +139,8 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId),
MTP_long(action.options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
SuggestToMTP(action.options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId);
@ -211,6 +215,9 @@ void SendExistingMedia(
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.suggest) {
sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post;
}
if (action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
@ -232,6 +239,7 @@ void SendExistingMedia(
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, media, caption);
const auto performRequest = [=](const auto &repeatRequest) -> void {
@ -255,7 +263,8 @@ void SendExistingMedia(
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId),
MTP_long(action.options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
SuggestToMTP(action.options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (error.code() == 400
@ -391,6 +400,9 @@ bool SendDice(MessageToSend &message) {
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.suggest) {
sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post;
}
if (action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
@ -415,6 +427,7 @@ bool SendDice(MessageToSend &message) {
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, TextWithEntities(), MTP_messageMediaDice(
MTP_int(0),
MTP_string(emoji)));
@ -435,7 +448,8 @@ bool SendDice(MessageToSend &message) {
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId),
MTP_long(action.options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
SuggestToMTP(action.options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId, newId);
@ -624,6 +638,7 @@ void SendConfirmedFile(
edition.useSameMarkup = true;
edition.useSameReplies = true;
edition.useSameReactions = true;
edition.useSameSuggest = true;
edition.savePreviousMedia = true;
itemToEdit->applyEdition(std::move(edition));
} else {
@ -640,6 +655,7 @@ void SendConfirmedFile(
.postAuthor = NewMessagePostAuthor(action),
.groupedId = groupId,
.effectId = file->to.options.effectId,
.suggest = HistoryMessageSuggestInfo(file->to.options),
}, caption, media);
}

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_statistics.h"
#include "api/api_credits_history_entry.h"
#include "api/api_statistics_data_deserialize.h"
#include "apiwrap.h"
#include "base/unixtime.h"
@ -695,19 +696,23 @@ rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
makeRequest(MTPstats_GetBroadcastRevenueStats(
MTP_flags(0),
api().request(MTPpayments_GetStarsRevenueStats(
MTP_flags(MTPpayments_getStarsRevenueStats::Flag::f_ton),
(_isUser ? user()->input : channel()->input)
)).done([=](const MTPstats_BroadcastRevenueStats &result) {
)).done([=](const MTPpayments_StarsRevenueStats &result) {
const auto &data = result.data();
const auto &balances = data.vbalances().data();
const auto &balances = data.vstatus().data();
const auto amount = [](const auto &a) {
return CreditsAmountFromTL(a);
};
_data = Data::EarnStatistics{
.topHoursGraph = StatisticalGraphFromTL(
data.vtop_hours_graph()),
.topHoursGraph = data.vtop_hours_graph()
? StatisticalGraphFromTL(*data.vtop_hours_graph())
: Data::StatisticalGraph(),
.revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()),
.currentBalance = balances.vcurrent_balance().v,
.availableBalance = balances.vavailable_balance().v,
.overallRevenue = balances.voverall_revenue().v,
.currentBalance = amount(balances.vcurrent_balance()),
.availableBalance = amount(balances.vavailable_balance()),
.overallRevenue = amount(balances.voverall_revenue()),
.usdRate = data.vusd_rate().v,
};
@ -745,62 +750,35 @@ void EarnStatistics::requestHistory(
if (_requestId) {
return;
}
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
constexpr auto kTlLimit = tl::make_int(kLimit);
_requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
_requestId = api().request(MTPpayments_GetStarsTransactions(
MTP_flags(MTPpayments_getStarsTransactions::Flag::f_ton),
MTP_string(), // Subscription ID.
(_isUser ? user()->input : channel()->input),
MTP_int(token),
(!token) ? kTlFirstSlice : kTlLimit
)).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
MTP_string(token),
token.isEmpty() ? kTlFirstSlice : kTlLimit
)).done([=](const MTPpayments_StarsStatus &result) {
_requestId = 0;
const auto &tlTransactions = result.data().vtransactions().v;
const auto nextToken = result.data().vnext_offset().value_or_empty();
auto list = std::vector<Data::EarnHistoryEntry>();
list.reserve(tlTransactions.size());
for (const auto &tlTransaction : tlTransactions) {
list.push_back(tlTransaction.match([&](
const MTPDbroadcastRevenueTransactionProceeds &d) {
return Data::EarnHistoryEntry{
.type = Data::EarnHistoryEntry::Type::In,
.amount = d.vamount().v,
.date = base::unixtime::parse(d.vfrom_date().v),
.dateTo = base::unixtime::parse(d.vto_date().v),
};
}, [&](const MTPDbroadcastRevenueTransactionWithdrawal &d) {
return Data::EarnHistoryEntry{
.type = Data::EarnHistoryEntry::Type::Out,
.status = d.is_pending()
? Data::EarnHistoryEntry::Status::Pending
: d.is_failed()
? Data::EarnHistoryEntry::Status::Failed
: Data::EarnHistoryEntry::Status::Success,
.amount = (std::numeric_limits<Data::EarnInt>::max()
- d.vamount().v
+ 1),
.date = base::unixtime::parse(d.vdate().v),
// .provider = qs(d.vprovider()),
.successDate = d.vtransaction_date()
? base::unixtime::parse(d.vtransaction_date()->v)
: QDateTime(),
.successLink = d.vtransaction_url()
? qs(*d.vtransaction_url())
: QString(),
};
}, [&](const MTPDbroadcastRevenueTransactionRefund &d) {
return Data::EarnHistoryEntry{
.type = Data::EarnHistoryEntry::Type::Return,
.amount = d.vamount().v,
.date = base::unixtime::parse(d.vdate().v),
// .provider = qs(d.vprovider()),
};
}));
}
const auto nextToken = token + tlTransactions.size();
const auto tlTransactions
= result.data().vhistory().value_or_empty();
const auto peer = _isUser ? (PeerData*)user() : (PeerData*)channel();
auto list = ranges::views::all(
tlTransactions
) | ranges::views::transform([=](const auto &d) {
return CreditsHistoryEntryFromTL(d, peer);
}) | ranges::to_vector;
done(Data::EarnHistorySlice{
.list = std::move(list),
.total = result.data().vcount().v,
.allLoaded = (result.data().vcount().v == nextToken),
.total = int(tlTransactions.size()),
// .total = result.data().vcount().v,
.allLoaded = nextToken.isEmpty(),
.token = Data::EarnHistorySlice::OffsetToken(nextToken),
});
}).fail([=] {

View file

@ -0,0 +1,638 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_suggest_post.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "chat_helpers/message_field.h"
#include "core/click_handler_types.h"
#include "data/components/credits.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_saved_sublist.h"
#include "history/view/controls/history_view_suggest_options.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwindow.h"
#include "settings/settings_credits_graphics.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/layers/generic_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace Api {
namespace {
void SendApproval(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
TimeId scheduleDate = 0) {
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion
|| suggestion->accepted
|| suggestion->rejected
|| suggestion->requestId) {
return;
}
const auto id = item->fullId();
const auto session = &show->session();
const auto finish = [=] {
if (const auto item = session->data().message(id)) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (suggestion) {
suggestion->requestId = 0;
}
}
};
suggestion->requestId = session->api().request(
MTPmessages_ToggleSuggestedPostApproval(
MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag()),
item->history()->peer->input,
MTP_int(item->id.bare),
MTP_int(scheduleDate),
MTPstring()) // reject_comment
).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
finish();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
finish();
}).send();
}
void ConfirmApproval(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
TimeId scheduleDate = 0,
Fn<void()> accepted = nullptr) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion
|| suggestion->accepted
|| suggestion->rejected
|| suggestion->requestId) {
return;
}
const auto id = item->fullId();
const auto price = suggestion->price;
const auto admin = item->history()->amMonoforumAdmin();
if (!admin && !price.empty()) {
const auto credits = &item->history()->session().credits();
if (price.ton()) {
if (!credits->tonLoaded()) {
credits->tonLoad();
return;
} else if (price > credits->tonBalance()) {
const auto peer = item->history()->peer;
show->show(
Box(HistoryView::InsufficientTonBox, peer, price));
return;
}
} else {
if (!credits->loaded()) {
credits->load();
return;
} else if (price > credits->balance()) {
using namespace Settings;
const auto peer = item->history()->peer;
const auto broadcast = peer->monoforumBroadcast();
const auto broadcastId = (broadcast ? broadcast : peer)->id;
const auto done = [=](SmallBalanceResult result) {
if (result == SmallBalanceResult::Success
|| result == SmallBalanceResult::Already) {
const auto item = peer->owner().message(id);
if (item) {
ConfirmApproval(
show,
item,
scheduleDate,
accepted);
}
}
};
MaybeRequestBalanceIncrease(
show,
int(base::SafeRound(price.value())),
SmallBalanceForSuggest{ broadcastId },
done);
return;
}
}
}
const auto peer = item->history()->peer;
const auto session = &peer->session();
const auto broadcast = peer->monoforumBroadcast();
const auto channelName = (broadcast ? broadcast : peer)->name();
const auto amount = admin
? HistoryView::PriceAfterCommission(session, price)
: price;
const auto commission = HistoryView::FormatAfterCommissionPercent(
session,
price);
const auto date = langDateTime(base::unixtime::parse(scheduleDate));
show->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto callback = std::make_shared<Fn<void()>>();
auto text = admin
? tr::lng_suggest_accept_text(
tr::now,
lt_from,
Ui::Text::Bold(item->from()->shortName()),
Ui::Text::WithEntities)
: tr::lng_suggest_accept_text_to(
tr::now,
lt_channel,
Ui::Text::Bold(channelName),
Ui::Text::WithEntities);
if (price) {
text.append("\n\n").append(admin
? (scheduleDate
? (amount.stars()
? tr::lng_suggest_accept_receive_stars
: tr::lng_suggest_accept_receive_ton)(
tr::now,
lt_count_decimal,
amount.value(),
lt_channel,
Ui::Text::Bold(channelName),
lt_percent,
TextWithEntities{ commission },
lt_date,
Ui::Text::Bold(date),
Ui::Text::RichLangValue)
: (amount.stars()
? tr::lng_suggest_accept_receive_now_stars
: tr::lng_suggest_accept_receive_now_ton)(
tr::now,
lt_count_decimal,
amount.value(),
lt_channel,
Ui::Text::Bold(channelName),
lt_percent,
TextWithEntities{ commission },
Ui::Text::RichLangValue))
: (scheduleDate
? (amount.stars()
? tr::lng_suggest_accept_pay_stars
: tr::lng_suggest_accept_pay_ton)(
tr::now,
lt_count_decimal,
amount.value(),
lt_date,
Ui::Text::Bold(date),
Ui::Text::RichLangValue)
: (amount.stars()
? tr::lng_suggest_accept_pay_now_stars
: tr::lng_suggest_accept_pay_now_ton)(
tr::now,
lt_count_decimal,
amount.value(),
Ui::Text::RichLangValue)));
if (admin) {
text.append(' ').append(
tr::lng_suggest_accept_receive_if(
tr::now,
Ui::Text::RichLangValue));
if (price.stars()) {
text.append("\n\n").append(
tr::lng_suggest_options_stars_warning(
tr::now,
Ui::Text::RichLangValue));
}
}
}
Ui::ConfirmBox(box, {
.text = text,
.confirmed = [=](Fn<void()> close) { (*callback)(); close(); },
.confirmText = tr::lng_suggest_accept_send(),
.title = tr::lng_suggest_accept_title(),
});
*callback = [=, weak = Ui::MakeWeak(box)] {
if (const auto onstack = accepted) {
onstack();
}
const auto item = show->session().data().message(id);
if (!item) {
return;
}
SendApproval(show, item, scheduleDate);
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
}));
}
void SendDecline(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
const QString &comment) {
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion
|| suggestion->accepted
|| suggestion->rejected
|| suggestion->requestId) {
return;
}
const auto id = item->fullId();
const auto session = &show->session();
const auto finish = [=] {
if (const auto item = session->data().message(id)) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (suggestion) {
suggestion->requestId = 0;
}
}
};
suggestion->requestId = session->api().request(
MTPmessages_ToggleSuggestedPostApproval(
MTP_flags(Flag::f_reject
| (comment.isEmpty() ? Flag() : Flag::f_reject_comment)),
item->history()->peer->input,
MTP_int(item->id.bare),
MTPint(), // schedule_date
MTP_string(comment))
).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
finish();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
finish();
}).send();
}
void RequestApprovalDate(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto id = item->fullId();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto close = [=] {
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
const auto done = [=](TimeId result) {
if (const auto item = show->session().data().message(id)) {
ConfirmApproval(show, item, result, close);
} else {
close();
}
};
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &show->session(),
.done = done,
.mode = SuggestMode::Publish,
});
*weak = dateBox.data();
show->show(std::move(dateBox));
}
void RequestDeclineComment(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto id = item->fullId();
const auto admin = item->history()->amMonoforumAdmin();
const auto peer = item->history()->peer;
const auto broadcast = peer->monoforumBroadcast();
const auto channelName = (broadcast ? broadcast : peer)->name();
show->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto callback = std::make_shared<Fn<void()>>();
Ui::ConfirmBox(box, {
.text = (admin
? tr::lng_suggest_decline_text(
lt_from,
rpl::single(Ui::Text::Bold(item->from()->shortName())),
Ui::Text::WithEntities)
: tr::lng_suggest_decline_text_to(
lt_channel,
rpl::single(Ui::Text::Bold(channelName)),
Ui::Text::WithEntities)),
.confirmed = [=](Fn<void()> close) { (*callback)(); close(); },
.confirmText = tr::lng_suggest_action_decline(),
.confirmStyle = &st::attentionBoxButton,
.title = tr::lng_suggest_decline_title(),
});
const auto reason = box->addRow(object_ptr<Ui::InputField>(
box,
st::factcheckField,
Ui::InputField::Mode::NoNewlines,
tr::lng_suggest_decline_reason()));
box->setFocusCallback([=] {
reason->setFocusFast();
});
*callback = [=, weak = Ui::MakeWeak(box)] {
const auto item = show->session().data().message(id);
if (!item) {
return;
}
SendDecline(show, item, reason->getLastText().trimmed());
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
reason->submits(
) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) {
if (!(modifiers & Qt::ShiftModifier)) {
(*callback)();
}
}, box->lifetime());
}));
}
struct SendSuggestState {
SendPaymentHelper sendPayment;
};
void SendSuggest(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
std::shared_ptr<SendSuggestState> state,
Fn<void(SuggestPostOptions&)> modify,
Fn<void()> done = nullptr,
int starsApproved = 0) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
const auto id = item->fullId();
const auto withPaymentApproved = [=](int stars) {
if (const auto item = show->session().data().message(id)) {
SendSuggest(show, item, state, modify, done, stars);
}
};
const auto isForward = item->Get<HistoryMessageForwarded>();
auto action = SendAction(item->history());
action.options.suggest.exists = 1;
if (suggestion) {
action.options.suggest.date = suggestion->date;
action.options.suggest.priceWhole = suggestion->price.whole();
action.options.suggest.priceNano = suggestion->price.nano();
action.options.suggest.ton = suggestion->price.ton() ? 1 : 0;
}
modify(action.options.suggest);
action.options.starsApproved = starsApproved;
action.replyTo.monoforumPeerId = item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId();
action.replyTo.messageId = item->fullId();
const auto checked = state->sendPayment.check(
show,
item->history()->peer,
action.options,
1,
withPaymentApproved);
if (!checked) {
return;
}
show->session().api().sendAction(action);
show->session().api().forwardMessages({
.items = { item },
.options = (isForward
? Data::ForwardOptions::PreserveInfo
: Data::ForwardOptions::NoSenderNames),
}, action);
if (const auto onstack = done) {
onstack();
}
}
void SuggestApprovalDate(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto id = item->fullId();
const auto state = std::make_shared<SendSuggestState>();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](TimeId result) {
const auto item = show->session().data().message(id);
if (!item) {
return;
}
const auto close = [=] {
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
SendSuggest(
show,
item,
state,
[=](SuggestPostOptions &options) { options.date = result; },
close);
};
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &show->session(),
.done = done,
.value = suggestion->date,
.mode = SuggestMode::Change,
});
*weak = dateBox.data();
show->show(std::move(dateBox));
}
void SuggestOfferForMessage(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
SuggestPostOptions values,
HistoryView::SuggestMode mode) {
const auto id = item->fullId();
const auto state = std::make_shared<SendSuggestState>();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](SuggestPostOptions result) {
const auto item = show->session().data().message(id);
if (!item) {
return;
}
const auto close = [=] {
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
SendSuggest(
show,
item,
state,
[=](SuggestPostOptions &options) { options = result; },
close);
};
using namespace HistoryView;
auto priceBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.peer = item->history()->peer,
.done = done,
.value = values,
.mode = mode,
});
*weak = priceBox.data();
show->show(std::move(priceBox));
}
void SuggestApprovalPrice(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
using namespace HistoryView;
SuggestOfferForMessage(show, item, {
.exists = uint32(1),
.priceWhole = uint32(suggestion->price.whole()),
.priceNano = uint32(suggestion->price.nano()),
.ton = uint32(suggestion->price.ton() ? 1 : 0),
.date = suggestion->date,
}, SuggestMode::Change);
}
} // namespace
std::shared_ptr<ClickHandler> AcceptClickHandler(
not_null<HistoryItem*> item) {
const auto session = &item->history()->session();
const auto id = item->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
if (!controller || &controller->session() != session) {
return;
}
const auto item = session->data().message(id);
if (!item) {
return;
}
const auto show = controller->uiShow();
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
} else if (!suggestion->date) {
RequestApprovalDate(show, item);
} else {
ConfirmApproval(show, item);
}
});
}
std::shared_ptr<ClickHandler> DeclineClickHandler(
not_null<HistoryItem*> item) {
const auto session = &item->history()->session();
const auto id = item->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
if (!controller || &controller->session() != session) {
return;
}
const auto item = session->data().message(id);
if (!item) {
return;
}
RequestDeclineComment(controller->uiShow(), item);
});
}
std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
not_null<HistoryItem*> item) {
const auto session = &item->history()->session();
const auto id = item->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto window = my.sessionWindow.get();
if (!window || &window->session() != session) {
return;
}
const auto item = session->data().message(id);
if (!item) {
return;
}
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
window->widget(),
st::popupMenuWithIcons);
if (HistoryView::CanEditSuggestedMessage(item)) {
menu->addAction(tr::lng_suggest_menu_edit_message(tr::now), [=] {
const auto item = session->data().message(id);
if (!item) {
return;
}
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto history = item->history();
const auto editData = PrepareEditText(item);
const auto cursor = MessageCursor{
int(editData.text.size()),
int(editData.text.size()),
Ui::kQFixedMax
};
const auto monoforumPeerId = history->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId();
const auto previewDraft = Data::WebPageDraft::FromItem(item);
history->setLocalEditDraft(std::make_unique<Data::Draft>(
editData,
FullReplyTo{
.messageId = FullMsgId(history->peer->id, item->id),
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions{
.exists = uint32(1),
.priceWhole = uint32(suggestion->price.whole()),
.priceNano = uint32(suggestion->price.nano()),
.ton = uint32(suggestion->price.ton() ? 1 : 0),
.date = suggestion->date,
},
cursor,
previewDraft));
history->session().changes().entryUpdated(
(monoforumPeerId
? item->savedSublist()
: (Data::Thread*)history.get()),
Data::EntryUpdate::Flag::LocalDraftSet);
}, &st::menuIconEdit);
}
menu->addAction(tr::lng_suggest_menu_edit_price(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalPrice(window->uiShow(), item);
}
}, &st::menuIconTagSell);
menu->addAction(tr::lng_suggest_menu_edit_time(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalDate(window->uiShow(), item);
}
}, &st::menuIconSchedule);
menu->popup(QCursor::pos());
});
}
void AddOfferToMessage(
std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId) {
const auto session = &show->session();
const auto item = session->data().message(itemId);
if (!item || !HistoryView::CanAddOfferToMessage(item)) {
return;
}
SuggestOfferForMessage(show, item, {}, HistoryView::SuggestMode::New);
}
} // namespace Api

View file

@ -0,0 +1,29 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class ClickHandler;
namespace Main {
class SessionShow;
} // namespace Main
namespace Api {
[[nodiscard]] std::shared_ptr<ClickHandler> AcceptClickHandler(
not_null<HistoryItem*> item);
[[nodiscard]] std::shared_ptr<ClickHandler> DeclineClickHandler(
not_null<HistoryItem*> item);
[[nodiscard]] std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
not_null<HistoryItem*> item);
void AddOfferToMessage(
std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId);
} // namespace Api

View file

@ -0,0 +1,257 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_todo_lists.h"
#include "api/api_editing.h"
#include "apiwrap.h"
#include "base/random.h"
#include "data/business/data_shortcut_messages.h" // ShortcutIdToMTP
#include "data/data_changes.h"
#include "data/data_histories.h"
#include "data/data_todo_list.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h" // ShouldSendSilent
#include "main/main_session.h"
namespace Api {
namespace {
constexpr auto kSendTogglesDelay = 3 * crl::time(1000);
[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) {
return TimeId(msgId >> 32);
}
} // namespace
TodoLists::TodoLists(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance())
, _sendTimer([=] { sendAccumulatedToggles(false); }) {
}
void TodoLists::create(
const TodoListData &data,
SendAction action,
Fn<void()> done,
Fn<void(QString)> fail) {
_session->api().sendAction(action);
const auto history = action.history;
const auto peer = history->peer;
const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId
: 0;
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto clearCloudDraft = action.clearDraft;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearLocalDraft(topicRootId, monoforumPeerId);
history->clearCloudDraft(topicRootId, monoforumPeerId);
history->startSavingCloudDraft(topicRootId, monoforumPeerId);
}
const auto silentPost = ShouldSendSilent(peer, action.options);
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
if (action.options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
if (action.options.shortcutId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.suggest) {
sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
const auto sendAs = action.options.sendAs;
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
auto &histories = history->owner().histories();
const auto randomId = base::RandomValue<uint64>();
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
TodoListDataToInputMedia(&data),
MTP_string(),
MTP_long(randomId),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTP_long(action.options.effectId),
MTP_long(starsPaid),
SuggestToMTP(action.options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
}
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
if (const auto onstack = done) {
onstack();
}
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
}
if (const auto onstack = fail) {
onstack(error.type());
}
});
}
void TodoLists::edit(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void()> done,
Fn<void(QString)> fail) {
EditTodoList(item, data, options, [=](mtpRequestId) {
if (const auto onstack = done) {
onstack();
}
}, [=](const QString &error, mtpRequestId) {
if (const auto onstack = fail) {
onstack(error);
}
});
}
void TodoLists::add(
not_null<HistoryItem*> item,
const std::vector<TodoListItem> &items,
Fn<void()> done,
Fn<void(QString)> fail) {
if (items.empty()) {
return;
}
const auto session = _session;
_session->api().request(MTPmessages_AppendTodoList(
item->history()->peer->input,
MTP_int(item->id.bare),
TodoListItemsToMTP(&item->history()->session(), items)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
if (const auto onstack = done) {
onstack();
}
}).fail([=](const MTP::Error &error) {
if (const auto onstack = fail) {
onstack(error.type());
}
}).send();
}
void TodoLists::toggleCompletion(FullMsgId itemId, int id, bool completed) {
auto &entry = _toggles[itemId];
if (completed) {
const auto changed1 = entry.completed.emplace(id).second;
const auto changed2 = entry.incompleted.remove(id);
if (!changed1 && !changed2) {
return;
}
} else {
const auto changed1 = entry.incompleted.emplace(id).second;
const auto changed2 = entry.completed.remove(id);
if (!changed1 && !changed2) {
return;
}
}
entry.scheduled = crl::now();
if (!entry.requestId && !_sendTimer.isActive()) {
_sendTimer.callOnce(kSendTogglesDelay);
}
}
void TodoLists::sendAccumulatedToggles(bool force) {
const auto now = crl::now();
auto nearest = crl::time(0);
for (auto &[itemId, entry] : _toggles) {
if (entry.requestId) {
continue;
}
const auto wait = entry.scheduled + kSendTogglesDelay - now;
if (wait <= 0) {
entry.scheduled = 0;
send(itemId, entry);
} else if (!nearest || nearest > wait) {
nearest = wait;
}
}
if (nearest > 0) {
_sendTimer.callOnce(nearest);
}
}
void TodoLists::send(FullMsgId itemId, Accumulated &entry) {
const auto item = _session->data().message(itemId);
if (!item) {
return;
}
auto completed = entry.completed
| ranges::views::transform([](int id) { return MTP_int(id); });
auto incompleted = entry.incompleted
| ranges::views::transform([](int id) { return MTP_int(id); });
entry.requestId = _api.request(MTPmessages_ToggleTodoCompleted(
item->history()->peer->input,
MTP_int(item->id),
MTP_vector_from_range(completed),
MTP_vector_from_range(incompleted)
)).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result);
finishRequest(itemId);
}).fail([=](const MTP::Error &error) {
finishRequest(itemId);
}).send();
entry.completed.clear();
entry.incompleted.clear();
}
void TodoLists::finishRequest(FullMsgId itemId) {
auto &entry = _toggles[itemId];
entry.requestId = 0;
if (entry.completed.empty() && entry.incompleted.empty()) {
_toggles.remove(itemId);
} else {
sendAccumulatedToggles(false);
}
}
} // namespace Api

View file

@ -0,0 +1,69 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "mtproto/sender.h"
class ApiWrap;
class HistoryItem;
struct TodoListItem;
struct TodoListData;
namespace Main {
class Session;
} // namespace Main
namespace Api {
struct SendAction;
struct SendOptions;
class TodoLists final {
public:
explicit TodoLists(not_null<ApiWrap*> api);
void create(
const TodoListData &data,
SendAction action,
Fn<void()> done,
Fn<void(QString)> fail);
void edit(
not_null<HistoryItem*> item,
const TodoListData &data,
SendOptions options,
Fn<void()> done,
Fn<void(QString)> fail);
void add(
not_null<HistoryItem*> item,
const std::vector<TodoListItem> &items,
Fn<void()> done,
Fn<void(QString)> fail);
void toggleCompletion(FullMsgId itemId, int id, bool completed);
private:
struct Accumulated {
base::flat_set<int> completed;
base::flat_set<int> incompleted;
crl::time scheduled = 0;
mtpRequestId requestId = 0;
};
void sendAccumulatedToggles(bool force);
void send(FullMsgId itemId, Accumulated &entry);
void finishRequest(FullMsgId itemId);
const not_null<Main::Session*> _session;
MTP::Sender _api;
base::flat_map<FullMsgId, Accumulated> _toggles;
base::Timer _sendTimer;
};
} // namespace Api

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "history/history.h"
@ -17,6 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_unread_things.h"
#include "apiwrap.h"
// AyuGram includes
#include "ayu/ayu_settings.h"
namespace Api {
namespace {
@ -31,12 +36,24 @@ UnreadThings::UnreadThings(not_null<ApiWrap*> api) : _api(api) {
bool UnreadThings::trackMentions(Data::Thread *thread) const {
const auto peer = thread ? thread->peer().get() : nullptr;
return peer && (peer->isChat() || peer->isMegagroup());
return peer
&& (peer->isChat() || peer->isMegagroup())
&& !peer->isMonoforum();
}
bool UnreadThings::trackReactions(Data::Thread *thread) const {
const auto peer = thread ? thread->peer().get() : nullptr;
return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup());
if (!peer) {
return false;
}
const auto &settings = AyuSettings::getInstance();
if (peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions) {
return false;
}
if (peer->isMegagroup() && !settings.showGroupReactions) {
return false;
}
return peer->isUser() || peer->isChat() || peer->isMegagroup();
}
void UnreadThings::preloadEnough(Data::Thread *thread) {
@ -93,7 +110,7 @@ void UnreadThings::cancelRequests(not_null<Data::Thread*> thread) {
void UnreadThings::requestMentions(
not_null<Data::Thread*> thread,
int loaded) {
if (_mentionsRequests.contains(thread)) {
if (_mentionsRequests.contains(thread) || thread->asSublist()) {
return;
}
const auto offsetId = std::max(
@ -138,12 +155,15 @@ void UnreadThings::requestReactions(
const auto maxId = 0;
const auto minId = 0;
const auto history = thread->owningHistory();
const auto sublist = thread->asSublist();
const auto topic = thread->asTopic();
using Flag = MTPmessages_GetUnreadReactions::Flag;
const auto requestId = _api->request(MTPmessages_GetUnreadReactions(
MTP_flags(topic ? Flag::f_top_msg_id : Flag()),
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
| (sublist ? Flag::f_saved_peer_id : Flag())),
history->peer->input,
MTP_int(topic ? topic->rootId() : 0),
(sublist ? sublist->sublistPeer()->input : MTPInputPeer()),
MTP_int(offsetId),
MTP_int(addOffset),
MTP_int(limit),

View file

@ -998,7 +998,7 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) {
});
// AyuGram sendOnlinePackets
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
const auto& config = _session->serverConfig();
bool isOnlineOrig = Core::App().hasActiveWindow(&session());
bool isOnline = settings.sendOnlinePackets && isOnlineOrig;
@ -1236,7 +1236,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPlong(), // effect
MTPFactCheck(),
MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MTPlong(), // paid_message_stars
MTPSuggestedPost()),
MessageFlags(),
NewMessageType::Unread);
} break;
@ -1275,7 +1276,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPlong(), // effect
MTPFactCheck(),
MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MTPlong(), // paid_message_stars
MTPSuggestedPost()),
MessageFlags(),
NewMessageType::Unread);
} break;
@ -1924,7 +1926,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
// Update web page anyway.
session().data().processWebpage(d.vwebpage());
session().data().sendWebPageGamePollNotifications();
session().data().sendWebPageGamePollTodoListNotifications();
updateAndApply(d.vpts().v, d.vpts_count().v, update);
} break;
@ -1934,7 +1936,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
// Update web page anyway.
session().data().processWebpage(d.vwebpage());
session().data().sendWebPageGamePollNotifications();
session().data().sendWebPageGamePollTodoListNotifications();
auto channel = session().data().channelLoaded(d.vchannel_id());
if (channel && !_handlingChannelDifference) {
@ -2450,6 +2452,32 @@ void Updates::feedUpdate(const MTPUpdate &update) {
session().data().updateRepliesReadTill({ id, readTillId, true });
} break;
case mtpc_updateReadMonoForumInbox: {
const auto &d = update.c_updateReadMonoForumInbox();
const auto parentChatId = ChannelId(d.vchannel_id());
const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id());
const auto readTillId = d.vread_max_id().v;
session().data().updateSublistReadTill({
parentChatId,
sublistPeerId,
readTillId,
false,
});
} break;
case mtpc_updateReadMonoForumOutbox: {
const auto &d = update.c_updateReadMonoForumOutbox();
const auto parentChatId = ChannelId(d.vchannel_id());
const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id());
const auto readTillId = d.vread_max_id().v;
session().data().updateSublistReadTill({
parentChatId,
sublistPeerId,
readTillId,
true,
});
} break;
case mtpc_updateChannelAvailableMessages: {
auto &d = update.c_updateChannelAvailableMessages();
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
@ -2669,13 +2697,22 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto &data = update.c_updateDraftMessage();
const auto peerId = peerFromMTP(data.vpeer());
const auto topicRootId = data.vtop_msg_id().value_or_empty();
const auto monoforumPeerId = data.vsaved_peer_id()
? peerFromMTP(*data.vsaved_peer_id())
: PeerId();
data.vdraft().match([&](const MTPDdraftMessage &data) {
Data::ApplyPeerCloudDraft(&session(), peerId, topicRootId, data);
Data::ApplyPeerCloudDraft(
&session(),
peerId,
topicRootId,
monoforumPeerId,
data);
}, [&](const MTPDdraftMessageEmpty &data) {
Data::ClearPeerCloudDraft(
&session(),
peerId,
topicRootId,
monoforumPeerId,
data.vdate().value_or_empty());
});
} break;

View file

@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "main/main_session.h"
// AyuGram includes
#include "ayu/ayu_settings.h"
namespace Api {
namespace {
@ -83,6 +87,8 @@ void ViewsManager::pollExtendedMedia(
}
void ViewsManager::viewsIncrement() {
const auto &settings = AyuSettings::getInstance();
for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) {
if (_incrementRequests.contains(i->first)) {
++i;
@ -97,7 +103,7 @@ void ViewsManager::viewsIncrement() {
const auto requestId = _api.request(MTPmessages_GetMessagesViews(
i->first->input,
MTP_vector<MTPint>(ids),
MTP_bool(true)
MTP_bool(settings.sendReadMessages)
)).done([=](
const MTPmessages_MessageViews &result,
mtpRequestId requestId) {

View file

@ -712,7 +712,8 @@ bool WhoReadExists(not_null<HistoryItem*> item) {
const auto megagroup = peer->asMegagroup();
if ((!chat && !megagroup)
|| (megagroup
&& (megagroup->flags() & ChannelDataFlag::ParticipantsHidden))) {
&& (megagroup->flags() & ChannelDataFlag::ParticipantsHidden))
|| (megagroup && megagroup->isMonoforum())) {
return false;
}
const auto &appConfig = peer->session().appConfig();

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_polls.h"
#include "api/api_sending.h"
#include "api/api_text_entities.h"
#include "api/api_todo_lists.h"
#include "api/api_self_destruct.h"
#include "api/api_sensitive_content.h"
#include "api/api_global_privacy.h"
@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_forum.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_search_controller.h"
#include "data/data_session.h"
@ -90,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ayu/ayu_settings.h"
#include "ayu/ayu_worker.h"
#include "ayu/utils/telegram_helpers.h"
#include "ayu/features/forward/ayu_forward.h"
namespace {
@ -183,6 +186,7 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
, _peerPhoto(std::make_unique<Api::PeerPhoto>(this))
, _polls(std::make_unique<Api::Polls>(this))
, _todoLists(std::make_unique<Api::TodoLists>(this))
, _chatParticipants(std::make_unique<Api::ChatParticipants>(this))
, _unreadThings(std::make_unique<Api::UnreadThings>(this))
, _ringtones(std::make_unique<Api::Ringtones>(this))
@ -327,7 +331,7 @@ void ApiWrap::checkChatInvite(
request(base::take(_checkInviteRequestId)).cancel();
_checkInviteRequestId = request(MTPmessages_CheckChatInvite(
MTP_string(hash)
)).done(std::move(done)).fail(std::move(fail)).send();
)).done(std::move(done)).fail(std::move(fail)).handleFloodErrors().send();
}
void ApiWrap::checkFilterInvite(
@ -387,10 +391,13 @@ void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
}
void ApiWrap::savePinnedOrder(not_null<Data::SavedMessages*> saved) {
if (saved->parentChat()) {
return;
}
const auto &order = _session->data().pinnedChatsOrder(saved);
const auto input = [](Dialogs::Key key) {
if (const auto sublist = key.sublist()) {
return MTP_inputDialogPeer(sublist->peer()->input);
return MTP_inputDialogPeer(sublist->sublistPeer()->input);
}
Unexpected("Key type in pinnedDialogsOrder().");
};
@ -426,7 +433,7 @@ void ApiWrap::toggleHistoryArchived(
if (archived) {
history->setFolder(_session->data().folder(archiveId));
} else {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (settings.hideAllChatsFolder) {
if (const auto window = Core::App().activeWindow()) {
if (const auto controller = window->sessionController()) {
@ -1303,7 +1310,7 @@ void ApiWrap::migrateFail(not_null<PeerData*> peer, const QString &error) {
void ApiWrap::markContentsRead(
const base::flat_set<not_null<HistoryItem*>> &items) {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
auto markedIds = QVector<MTPint>();
auto channelMarkedIds = base::flat_map<
@ -1349,7 +1356,7 @@ void ApiWrap::markContentsRead(not_null<HistoryItem*> item) {
return;
}
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!settings.sendReadMessages && !passthrough) {
return;
}
@ -1403,6 +1410,32 @@ void ApiWrap::deleteAllFromParticipantSend(
}).send();
}
void ApiWrap::deleteSublistHistory(
not_null<ChannelData*> channel,
not_null<PeerData*> sublistPeer) {
deleteSublistHistorySend(channel, sublistPeer);
}
void ApiWrap::deleteSublistHistorySend(
not_null<ChannelData*> parentChat,
not_null<PeerData*> sublistPeer) {
request(MTPmessages_DeleteSavedHistory(
MTP_flags(MTPmessages_DeleteSavedHistory::Flag::f_parent_peer),
parentChat->input,
sublistPeer->input,
MTP_int(0), // max_id
MTP_int(0), // min_date
MTP_int(0) // max_date
)).done([=](const MTPmessages_AffectedHistory &result) {
const auto offset = applyAffectedHistory(parentChat, result);
if (offset > 0) {
deleteSublistHistorySend(parentChat, sublistPeer);
} else if (const auto monoforum = parentChat->monoforum()) {
monoforum->applySublistDeleted(sublistPeer);
}
}).send();
}
void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) {
if (!_stickerSetRequests.contains(setId)) {
_stickerSetRequests.emplace(setId, StickerSetRequest{ access });
@ -1752,7 +1785,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
using Flag = ChannelDataFlag;
chatParticipants().loadSimilarPeers(channel);
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!settings.collapseSimilarChannels) {
channel->setFlags(channel->flags() | Flag::SimilarExpanded);
}
@ -2098,8 +2131,13 @@ void ApiWrap::saveCurrentDraftToCloud() {
_session->local().writeDrafts(history);
const auto topicRootId = thread->topicRootId();
const auto localDraft = history->localDraft(topicRootId);
const auto cloudDraft = history->cloudDraft(topicRootId);
const auto monoforumPeerId = thread->monoforumPeerId();
const auto localDraft = history->localDraft(
topicRootId,
monoforumPeerId);
const auto cloudDraft = history->cloudDraft(
topicRootId,
monoforumPeerId);
if (!Data::DraftsAreEqual(localDraft, cloudDraft)
&& !_session->supportMode()) {
saveDraftToCloudDelayed(thread);
@ -2122,15 +2160,22 @@ void ApiWrap::saveDraftsToCloud() {
const auto history = thread->owningHistory();
const auto topicRootId = thread->topicRootId();
auto cloudDraft = history->cloudDraft(topicRootId);
auto localDraft = history->localDraft(topicRootId);
const auto monoforumPeerId = thread->monoforumPeerId();
auto cloudDraft = history->cloudDraft(topicRootId, monoforumPeerId);
auto localDraft = history->localDraft(topicRootId, monoforumPeerId);
if (cloudDraft && cloudDraft->saveRequestId) {
request(base::take(cloudDraft->saveRequestId)).cancel();
}
if (!_session->supportMode()) {
cloudDraft = history->createCloudDraft(topicRootId, localDraft);
cloudDraft = history->createCloudDraft(
topicRootId,
monoforumPeerId,
localDraft);
} else if (!cloudDraft) {
cloudDraft = history->createCloudDraft(topicRootId, nullptr);
cloudDraft = history->createCloudDraft(
topicRootId,
monoforumPeerId,
nullptr);
}
auto flags = MTPmessages_SaveDraft::Flags(0);
@ -2140,18 +2185,23 @@ void ApiWrap::saveDraftsToCloud() {
} else if (!cloudDraft->webpage.url.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_media;
}
if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
if (cloudDraft->reply.messageId
|| cloudDraft->reply.topicRootId
|| cloudDraft->reply.monoforumPeerId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
}
if (!textWithTags.tags.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_entities;
}
if (cloudDraft->suggest) {
flags |= MTPmessages_SaveDraft::Flag::f_suggested_post;
}
auto entities = Api::EntitiesToMTP(
_session,
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags),
Api::ConvertOption::SkipLocal);
history->startSavingCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId, monoforumPeerId);
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
MTP_flags(flags),
ReplyToForMTP(history, cloudDraft->reply),
@ -2161,16 +2211,21 @@ void ApiWrap::saveDraftsToCloud() {
Data::WebPageForMTP(
cloudDraft->webpage,
textWithTags.text.isEmpty()),
MTP_long(0) // effect
MTP_long(0), // effect
Api::SuggestToMTP(cloudDraft->suggest)
)).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
const auto cloudDraft = history->cloudDraft(
topicRootId,
monoforumPeerId);
if (cloudDraft) {
if (cloudDraft->saveRequestId == requestId) {
cloudDraft->saveRequestId = 0;
history->draftSavedToCloud(topicRootId);
history->draftSavedToCloud(topicRootId, monoforumPeerId);
}
}
const auto i = _draftsSaveRequestIds.find(weak);
@ -2183,10 +2238,14 @@ void ApiWrap::saveDraftsToCloud() {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
const auto cloudDraft = history->cloudDraft(
topicRootId,
monoforumPeerId);
if (cloudDraft) {
if (cloudDraft->saveRequestId == requestId) {
history->clearCloudDraft(topicRootId);
history->clearCloudDraft(topicRootId, monoforumPeerId);
}
}
const auto i = _draftsSaveRequestIds.find(weak);
@ -2560,7 +2619,10 @@ void ApiWrap::refreshFileReference(
});
}
void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req) {
void ApiWrap::gotWebPages(
ChannelData *channel,
const MTPmessages_Messages &result,
mtpRequestId req) {
WebPageData::ApplyChanges(_session, channel, result);
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
if (i->second == req) {
@ -2574,7 +2636,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
++i;
}
}
_session->data().sendWebPageGamePollNotifications();
_session->data().sendWebPageGamePollTodoListNotifications();
}
void ApiWrap::updateStickers() {
@ -2964,17 +3026,27 @@ void ApiWrap::resolveJumpToDate(
Fn<void(not_null<PeerData*>, MsgId)> callback) {
if (const auto peer = chat.peer()) {
const auto topic = chat.topic();
const auto rootId = topic ? topic->rootId() : 0;
resolveJumpToHistoryDate(peer, rootId, date, std::move(callback));
const auto sublist = chat.sublist();
const auto rootId = topic ? topic->rootId() : MsgId();
const auto monoforumPeerId = sublist
? sublist->sublistPeer()->id
: PeerId();
resolveJumpToHistoryDate(
peer,
rootId,
monoforumPeerId,
date,
std::move(callback));
}
}
template <typename Callback>
void ApiWrap::requestMessageAfterDate(
not_null<PeerData*> peer,
MsgId topicRootId,
const QDate &date,
Callback &&callback) {
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
const QDate &date,
Callback &&callback) {
// API returns a message with date <= offset_date.
// So we request a message with offset_date = desired_date - 1 and add_offset = -1.
// This should give us the first message with date >= desired_date.
@ -2997,7 +3069,7 @@ void ApiWrap::requestMessageAfterDate(
return &messages.vmessages().v;
};
const auto list = result.match([&](
const MTPDmessages_messages &data) {
const MTPDmessages_messages &data) {
return handleMessages(data);
}, [&](const MTPDmessages_messagesSlice &data) {
return handleMessages(data);
@ -3040,6 +3112,18 @@ void ApiWrap::requestMessageAfterDate(
MTP_int(maxId),
MTP_int(minId),
MTP_long(historyHash)));
} else if (monoforumPeerId) {
send(MTPmessages_GetSavedHistory(
MTP_flags(MTPmessages_GetSavedHistory::Flag::f_parent_peer),
peer->input,
session().data().peer(monoforumPeerId)->input,
MTP_int(offsetId),
MTP_int(offsetDate),
MTP_int(addOffset),
MTP_int(limit),
MTP_int(maxId),
MTP_int(minId),
MTP_long(historyHash)));
} else {
send(MTPmessages_GetHistory(
peer->input,
@ -3056,28 +3140,41 @@ void ApiWrap::requestMessageAfterDate(
void ApiWrap::resolveJumpToHistoryDate(
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
const QDate &date,
Fn<void(not_null<PeerData*>, MsgId)> callback) {
if (const auto channel = peer->migrateTo()) {
return resolveJumpToHistoryDate(
channel,
topicRootId,
monoforumPeerId,
date,
std::move(callback));
}
const auto jumpToDateInPeer = [=] {
requestMessageAfterDate(peer, topicRootId, date, [=](MsgId itemId) {
callback(peer, itemId);
});
requestMessageAfterDate(
peer,
topicRootId,
monoforumPeerId,
date,
[=](MsgId itemId) { callback(peer, itemId); });
};
if (const auto chat = topicRootId ? nullptr : peer->migrateFrom()) {
requestMessageAfterDate(chat, 0, date, [=](MsgId itemId) {
if (itemId) {
callback(chat, itemId);
} else {
jumpToDateInPeer();
}
});
const auto migrated = (topicRootId || monoforumPeerId)
? nullptr
: peer->migrateFrom();
if (migrated) {
requestMessageAfterDate(
migrated,
MsgId(),
PeerId(),
date,
[=](MsgId itemId) {
if (itemId) {
callback(migrated, itemId);
} else {
jumpToDateInPeer();
}
});
} else {
jumpToDateInPeer();
}
@ -3126,12 +3223,14 @@ void ApiWrap::requestHistory(
void ApiWrap::requestSharedMedia(
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
SharedMediaType type,
MsgId messageId,
SliceType slice) {
const auto key = SharedMediaRequest{
peer,
topicRootId,
monoforumPeerId,
type,
messageId,
slice,
@ -3143,6 +3242,7 @@ void ApiWrap::requestSharedMedia(
const auto prepared = Api::PrepareSearchRequest(
peer,
topicRootId,
monoforumPeerId,
type,
QString(),
messageId,
@ -3165,7 +3265,12 @@ void ApiWrap::requestSharedMedia(
messageId,
slice,
result);
sharedMediaDone(peer, topicRootId, type, std::move(parsed));
sharedMediaDone(
peer,
topicRootId,
monoforumPeerId,
type,
std::move(parsed));
finish();
}).fail([=] {
_sharedMediaRequests.remove(key);
@ -3178,16 +3283,19 @@ void ApiWrap::requestSharedMedia(
void ApiWrap::sharedMediaDone(
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
SharedMediaType type,
Api::SearchResult &&parsed) {
const auto topic = peer->forumTopicFor(topicRootId);
if (topicRootId && !topic) {
const auto sublist = peer->monoforumSublistFor(monoforumPeerId);
if ((topicRootId && !topic) || (monoforumPeerId && !sublist)) {
return;
}
const auto hasMessages = !parsed.messageIds.empty();
_session->storage().add(Storage::SharedMediaAddSlice(
peer->id,
topicRootId,
monoforumPeerId,
type,
std::move(parsed.messageIds),
parsed.noSkipRange,
@ -3198,6 +3306,9 @@ void ApiWrap::sharedMediaDone(
if (topic) {
topic->setHasPinnedMessages(true);
}
if (sublist) {
sublist->setHasPinnedMessages(true);
}
}
}
@ -3234,8 +3345,14 @@ void ApiWrap::sendAction(const SendAction &action) {
const auto topic = topicRootId
? action.history->peer->forumTopicFor(topicRootId)
: nullptr;
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
const auto sublist = monoforumPeerId
? action.history->peer->monoforumSublistFor(monoforumPeerId)
: nullptr;
if (topic) {
topic->readTillEnd();
} else if (sublist) {
sublist->readTillEnd();
} else {
_session->data().histories().readInbox(action.history);
}
@ -3247,7 +3364,10 @@ void ApiWrap::sendAction(const SendAction &action) {
void ApiWrap::finishForwarding(const SendAction &action) {
const auto history = action.history;
const auto topicRootId = action.replyTo.topicRootId;
auto toForward = history->resolveForwardDraft(topicRootId);
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
auto toForward = history->resolveForwardDraft(
topicRootId,
monoforumPeerId);
if (!toForward.items.empty()) {
const auto error = GetErrorForSending(
history->peer,
@ -3260,7 +3380,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
}
forwardMessages(std::move(toForward), action);
history->setForwardDraft(topicRootId, {});
history->setForwardDraft(topicRootId, monoforumPeerId, {});
}
_session->data().sendHistoryChangeNotifications();
@ -3279,6 +3399,22 @@ void ApiWrap::forwardMessages(
FnMut<void()> &&successCallback) {
Expects(!draft.items.empty());
const auto fullAyuForward = AyuForward::isFullAyuForwardNeeded(draft.items.front());
if (fullAyuForward) {
crl::async([=] {
AyuForward::forwardMessages(_session, action, false, draft);
});
return;
}
const auto ayuIntelligentForwardNeeded = AyuForward::isAyuForwardNeeded(draft.items);
if (ayuIntelligentForwardNeeded) {
crl::async([=] {
AyuForward::intelligentForward(_session, action, draft);
});
return;
}
auto &histories = _session->data().histories();
struct SharedCallback {
@ -3329,6 +3465,9 @@ void ApiWrap::forwardMessages(
if (sendAs) {
sendFlags |= SendFlag::f_send_as;
}
if (action.options.suggest) {
sendFlags |= SendFlag::f_suggested_post;
}
const auto kGeneralId = Data::ForumTopic::kGeneralId;
const auto topicRootId = action.replyTo.topicRootId;
const auto topMsgId = (topicRootId == kGeneralId)
@ -3337,6 +3476,13 @@ void ApiWrap::forwardMessages(
if (topMsgId) {
sendFlags |= SendFlag::f_top_msg_id;
}
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
const auto monoforumPeer = monoforumPeerId
? session().data().peer(monoforumPeerId).get()
: nullptr;
if (monoforumPeer || (action.options.suggest && action.replyTo)) {
sendFlags |= SendFlag::f_reply_to;
}
auto forwardFrom = draft.items.front()->history()->peer;
auto ids = QVector<MTPint>();
@ -3366,11 +3512,17 @@ void ApiWrap::forwardMessages(
MTP_vector<MTPlong>(randomIds),
peer->input,
MTP_int(topMsgId),
(action.options.suggest
? ReplyToForMTP(history, action.replyTo)
: monoforumPeer
? MTP_inputReplyToMonoForum(monoforumPeer->input)
: MTPInputReplyTo()),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTPint(), // video_timestamp
MTP_long(starsPaid)
MTP_long(starsPaid),
Api::SuggestToMTP(action.options.suggest)
)).done([=](const MTPUpdates &result) {
if (!scheduled) {
this->updates().checkForSentToScheduled(result);
@ -3380,7 +3532,7 @@ void ApiWrap::forwardMessages(
shared->callback();
}
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!settings.sendReadMessages && settings.markReadAfterAction && history->lastMessage())
{
readHistory(history->lastMessage());
@ -3419,12 +3571,15 @@ void ApiWrap::forwardMessages(
.id = newId.msg,
.flags = flags,
.from = NewMessageFromId(action),
.replyTo = { .topicRootId = topMsgId },
.replyTo = {
.topicRootId = topMsgId,
.monoforumPeerId = monoforumPeerId,
},
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action),
.suggest = HistoryMessageSuggestInfo(action.options),
// forwarded messages don't have effects
//.effectId = action.options.effectId,
}, item);
@ -3519,6 +3674,7 @@ void ApiWrap::sendSharedContact(
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, TextWithEntities(), MTP_messageMediaContact(
MTP_string(phone),
MTP_string(firstName),
@ -3567,7 +3723,19 @@ void ApiWrap::editMedia(
if (list.files.empty()) return;
auto &file = list.files.front();
const auto to = FileLoadTaskOptions(action);
auto to = FileLoadTaskOptions(action);
const auto existing = to.replaceMediaOf
? session().data().message(action.history->peer, to.replaceMediaOf)
: nullptr;
if (existing && existing->computeSuggestionActions()
== SuggestionActions::AcceptAndDecline) {
to.replyTo.messageId = {
action.history->peer->id,
to.replaceMediaOf
};
to.replyTo.monoforumPeerId = existing->sublistPeerId();
to.replaceMediaOf = MsgId();
}
_fileLoader->addTask(std::make_unique<FileLoadTask>(
&session(),
file.path,
@ -3757,6 +3925,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
const auto clearCloudDraft = action.clearDraft;
const auto draftTopicRootId = action.replyTo.topicRootId;
const auto draftMonoforumPeerId = action.replyTo.monoforumPeerId;
const auto replyTo = action.replyTo.messageId
? peer->owner().message(action.replyTo.messageId)
: nullptr;
@ -3766,8 +3935,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
? replyTo->topicRootId()
: Data::ForumTopic::kGeneralId;
const auto topic = peer->forumTopicFor(topicRootId);
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|| Api::SendDice(message)) {
const bool canSendTexts = topic
? Data::CanSendTexts(topic)
: Data::CanSendTexts(peer);
if (!canSendTexts && !AyuForward::isForwarding(peer->id) || Api::SendDice(message)) {
return;
}
local().saveRecentSentHashtags(textWithTags.text);
@ -3866,8 +4039,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearCloudDraft(draftTopicRootId);
history->startSavingCloudDraft(draftTopicRootId);
history->clearCloudDraft(draftTopicRootId, draftMonoforumPeerId);
history->startSavingCloudDraft(
draftTopicRootId,
draftMonoforumPeerId);
}
const auto sendAs = action.options.sendAs;
if (sendAs) {
@ -3888,6 +4063,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.suggest) {
sendFlags |= MTPmessages_SendMessage::Flag::f_suggested_post;
mediaFlags |= MTPmessages_SendMedia::Flag::f_suggested_post;
}
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
@ -3906,6 +4085,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, sending, media);
const auto done = [=](
const MTPUpdates &result,
@ -3913,6 +4093,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
draftTopicRootId,
draftMonoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
}
@ -3929,6 +4110,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
draftTopicRootId,
draftMonoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
}
};
@ -3955,7 +4137,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
mtpShortcut,
MTP_long(action.options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
Api::SuggestToMTP(action.options.suggest)
), done, fail);
} else {
histories.sendPreparedMessage(
@ -3974,7 +4157,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
mtpShortcut,
MTP_long(action.options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
Api::SuggestToMTP(action.options.suggest)
), done, fail);
}
isFirst = false;
@ -4055,6 +4239,7 @@ void ApiWrap::sendInlineResult(
const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId
: 0;
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
using SendFlag = MTPmessages_SendInlineBotResult::Flag;
auto flags = NewMessageFlags(peer);
@ -4107,8 +4292,8 @@ void ApiWrap::sendInlineResult(
.postAuthor = NewMessagePostAuthor(action),
});
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
history->clearCloudDraft(topicRootId, monoforumPeerId);
history->startSavingCloudDraft(topicRootId, monoforumPeerId);
auto &histories = history->owner().histories();
histories.sendPreparedMessage(
@ -4129,6 +4314,7 @@ void ApiWrap::sendInlineResult(
), [=](const MTPUpdates &result, const MTP::Response &response) {
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(true);
@ -4137,6 +4323,7 @@ void ApiWrap::sendInlineResult(
sendMessageFail(error, peer, randomId, newId);
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(false);
@ -4290,6 +4477,7 @@ void ApiWrap::sendMediaWithRandomId(
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : Flag(0))
| (options.suggest ? Flag::f_suggested_post : Flag(0))
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
@ -4318,7 +4506,8 @@ void ApiWrap::sendMediaWithRandomId(
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
Api::SuggestToMTP(options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true);
if (updateRecentStickers) {
@ -4375,6 +4564,7 @@ void ApiWrap::sendMultiPaidMedia(
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : Flag(0))
| (options.suggest ? Flag::f_suggested_post : Flag(0))
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
@ -4402,7 +4592,8 @@ void ApiWrap::sendMultiPaidMedia(
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId),
MTP_long(starsPaid)
MTP_long(starsPaid),
Api::SuggestToMTP(options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (const auto album = _sendingAlbums.take(groupId)) {
const auto copy = (*album)->items;
@ -4742,6 +4933,10 @@ Api::Polls &ApiWrap::polls() {
return *_polls;
}
Api::TodoLists &ApiWrap::todoLists() {
return *_todoLists;
}
Api::ChatParticipants &ApiWrap::chatParticipants() {
return *_chatParticipants;
}

View file

@ -77,6 +77,7 @@ class ConfirmPhone;
class PeerPhoto;
class PeerColors;
class Polls;
class TodoLists;
class ChatParticipants;
class UnreadThings;
class Ringtones;
@ -231,6 +232,9 @@ public:
void deleteAllFromParticipant(
not_null<ChannelData*> channel,
not_null<PeerData*> from);
void deleteSublistHistory(
not_null<ChannelData*> parentChat,
not_null<PeerData*> sublistPeer);
void requestWebPageDelayed(not_null<WebPageData*> page);
void clearWebPageRequest(not_null<WebPageData*> page);
@ -286,6 +290,7 @@ public:
void requestSharedMedia(
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
Storage::SharedMediaType type,
MsgId messageId,
SliceType slice);
@ -409,6 +414,7 @@ public:
[[nodiscard]] Api::ConfirmPhone &confirmPhone();
[[nodiscard]] Api::PeerPhoto &peerPhoto();
[[nodiscard]] Api::Polls &polls();
[[nodiscard]] Api::TodoLists &todoLists();
[[nodiscard]] Api::ChatParticipants &chatParticipants();
[[nodiscard]] Api::UnreadThings &unreadThings();
[[nodiscard]] Api::Ringtones &ringtones();
@ -502,18 +508,21 @@ private:
void resolveJumpToHistoryDate(
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
const QDate &date,
Fn<void(not_null<PeerData*>, MsgId)> callback);
template <typename Callback>
void requestMessageAfterDate(
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
const QDate &date,
Callback &&callback);
void sharedMediaDone(
not_null<PeerData*> peer,
MsgId topicRootId,
PeerId monoforumPeerId,
SharedMediaType type,
Api::SearchResult &&parsed);
void globalMediaDone(
@ -539,6 +548,9 @@ private:
void deleteAllFromParticipantSend(
not_null<ChannelData*> channel,
not_null<PeerData*> from);
void deleteSublistHistorySend(
not_null<ChannelData*> parentChat,
not_null<PeerData*> sublistPeer);
void uploadAlbumMedia(
not_null<HistoryItem*> item,
@ -659,6 +671,7 @@ private:
struct SharedMediaRequest {
not_null<PeerData*> peer;
MsgId topicRootId = 0;
PeerId monoforumPeerId = 0;
SharedMediaType mediaType = {};
MsgId aroundId = 0;
SliceType sliceType = {};
@ -753,6 +766,7 @@ private:
const std::unique_ptr<Api::ConfirmPhone> _confirmPhone;
const std::unique_ptr<Api::PeerPhoto> _peerPhoto;
const std::unique_ptr<Api::Polls> _polls;
const std::unique_ptr<Api::TodoLists> _todoLists;
const std::unique_ptr<Api::ChatParticipants> _chatParticipants;
const std::unique_ptr<Api::UnreadThings> _unreadThings;
const std::unique_ptr<Api::Ringtones> _ringtones;

View file

@ -28,7 +28,7 @@ void initLang() {
}
void initUiSettings() {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
AyuUiSettings::setMonoFont(settings.monoFont);
AyuUiSettings::setWideMultiplier(settings.wideMultiplier);

View file

@ -107,7 +107,7 @@ void AyuLanguage::applyLanguageJson(QJsonDocument doc) {
const auto json = doc.object();
for (const QString &brokenKey : json.keys()) {
auto key = qsl("ayu_") + brokenKey;
const auto val = json.value(brokenKey).toString().replace(qsl("&amp;"), qsl("&")).toUtf8();
auto val = json.value(brokenKey).toString().replace(qsl("&amp;"), qsl("&"));
if (key.endsWith("_zero") || key.endsWith("_two") || key.endsWith("_few") || key.endsWith("_many")) {
continue;
@ -119,8 +119,16 @@ void AyuLanguage::applyLanguageJson(QJsonDocument doc) {
key = key.replace("_other", "#other");
}
if (val.contains(qsl("%1$d")) && !val.contains(qsl("%2$d"))) {
val = val.replace(qsl("%1$d"), qsl("{count}"));
} else if (val.contains(qsl("%1$d")) && val.contains(qsl("%2$d"))) {
val = val.replace(qsl("%1$d"), qsl("{count1}")).replace(qsl("%2$d"), qsl("{count2}"));
} else if (val.contains(qsl("%1$s"))) {
val = val.replace(qsl("%1$s"), qsl("{item}"));
}
Lang::GetInstance().resetValue(key.toUtf8());
Lang::GetInstance().applyValue(key.toUtf8(), val);
Lang::GetInstance().applyValue(key.toUtf8(), val.toUtf8());
}
Lang::GetInstance().updatePluralRules();
}

View file

@ -231,6 +231,8 @@ AyuGramSettings::AyuGramSettings() {
disableNotificationsDelay = false;
localPremium = false;
showChannelReactions = true;
showGroupReactions = true;
// ~ Customization
appIcon =
@ -417,8 +419,16 @@ void set_localPremium(bool val) {
settings->localPremium = val;
}
void set_appIcon(QString val) {
settings->appIcon = std::move(val);
void set_hideChannelReactions(bool val) {
settings->showChannelReactions = val;
}
void set_hideGroupReactions(bool val) {
settings->showGroupReactions = val;
}
void set_appIcon(const QString &val) {
settings->appIcon = val;
}
void set_simpleQuotesAndReplies(bool val) {
@ -429,13 +439,13 @@ void set_replaceBottomInfoWithIcons(bool val) {
settings->replaceBottomInfoWithIcons = val;
}
void set_deletedMark(QString val) {
settings->deletedMark = std::move(val);
void set_deletedMark(const QString &val) {
settings->deletedMark = val;
deletedMarkReactive = settings->deletedMark;
}
void set_editedMark(QString val) {
settings->editedMark = std::move(val);
void set_editedMark(const QString &val) {
settings->editedMark = val;
editedMarkReactive = settings->editedMark;
}
@ -522,7 +532,7 @@ void set_showStreamerToggleInTray(bool val) {
settings->showStreamerToggleInTray = val;
}
void set_monoFont(QString val) {
void set_monoFont(const QString &val) {
settings->monoFont = val;
}

View file

@ -8,6 +8,33 @@
#include "ayu/libs/json.hpp"
#include "ayu/libs/json_ext.hpp"
// json.hpp in some build environments may not provide helper macros.
// To ensure successful compilation, define them here when missing.
#ifndef NLOHMANN_JSON_TO
#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1;
#endif
#ifndef NLOHMANN_JSON_FROM
#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1);
#endif
#ifndef NLOHMANN_JSON_FROM_WITH_DEFAULT
#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) \
nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1);
#endif
#ifndef NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \
inline void to_json(nlohmann::json& nlohmann_json_j, \
const Type& nlohmann_json_t) { \
NLOHMANN_JSON_EXPAND( \
NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) \
} \
inline void from_json(const nlohmann::json& nlohmann_json_j, \
Type& nlohmann_json_t) { \
const Type nlohmann_json_default_obj{}; \
NLOHMANN_JSON_EXPAND( \
NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) \
}
#endif
#include "rpl/producer.h"
namespace AyuSettings {
@ -49,6 +76,8 @@ public:
bool disableNotificationsDelay;
bool localPremium;
bool showChannelReactions;
bool showGroupReactions;
QString appIcon;
bool simpleQuotesAndReplies;
@ -131,12 +160,14 @@ void set_increaseWebviewWidth(bool val);
void set_disableNotificationsDelay(bool val);
void set_localPremium(bool val);
void set_hideChannelReactions(bool val);
void set_hideGroupReactions(bool val);
void set_appIcon(QString val);
void set_appIcon(const QString &val);
void set_simpleQuotesAndReplies(bool val);
void set_replaceBottomInfoWithIcons(bool val);
void set_deletedMark(QString val);
void set_editedMark(QString val);
void set_deletedMark(const QString &val);
void set_editedMark(const QString &val);
void set_recentStickersCount(int val);
void set_showReactionsPanelInContextMenu(int val);
@ -162,7 +193,7 @@ void set_showStreamerToggleInDrawer(bool val);
void set_showGhostToggleInTray(bool val);
void set_showStreamerToggleInTray(bool val);
void set_monoFont(QString val);
void set_monoFont(const QString &val);
void set_hideNotificationCounters(bool val);
void set_hideNotificationBadge(bool val);
@ -178,68 +209,134 @@ void set_stickerConfirmation(bool val);
void set_gifConfirmation(bool val);
void set_voiceConfirmation(bool val);
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
AyuGramSettings,
sendReadMessages,
sendReadStories,
sendOnlinePackets,
sendUploadProgress,
sendOfflinePacketAfterOnline,
markReadAfterAction,
useScheduledMessages,
sendWithoutSound,
saveDeletedMessages,
saveMessagesHistory,
saveForBots,
hideFromBlocked,
disableAds,
disableStories,
disableCustomBackgrounds,
showOnlyAddedEmojisAndStickers,
collapseSimilarChannels,
hideSimilarChannels,
wideMultiplier,
spoofWebviewAsAndroid,
increaseWebviewHeight,
increaseWebviewWidth,
disableNotificationsDelay,
localPremium,
appIcon,
simpleQuotesAndReplies,
replaceBottomInfoWithIcons,
deletedMark,
editedMark,
recentStickersCount,
showReactionsPanelInContextMenu,
showViewsPanelInContextMenu,
showHideMessageInContextMenu,
showUserMessagesInContextMenu,
showMessageDetailsInContextMenu,
showAttachButtonInMessageField,
showCommandsButtonInMessageField,
showEmojiButtonInMessageField,
showMicrophoneButtonInMessageField,
showAutoDeleteButtonInMessageField,
showAttachPopup,
showEmojiPopup,
showLReadToggleInDrawer,
showSReadToggleInDrawer,
showGhostToggleInDrawer,
showStreamerToggleInDrawer,
showGhostToggleInTray,
showStreamerToggleInTray,
monoFont,
hideNotificationCounters,
hideNotificationBadge,
hideAllChatsFolder,
channelBottomButton,
showPeerId,
showMessageSeconds,
showMessageShot,
stickerConfirmation,
gifConfirmation,
voiceConfirmation
);
inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nlohmann_json_t) {
NLOHMANN_JSON_TO(sendReadMessages)
NLOHMANN_JSON_TO(sendReadStories)
NLOHMANN_JSON_TO(sendOnlinePackets)
NLOHMANN_JSON_TO(sendUploadProgress)
NLOHMANN_JSON_TO(sendOfflinePacketAfterOnline)
NLOHMANN_JSON_TO(markReadAfterAction)
NLOHMANN_JSON_TO(useScheduledMessages)
NLOHMANN_JSON_TO(sendWithoutSound)
NLOHMANN_JSON_TO(saveDeletedMessages)
NLOHMANN_JSON_TO(saveMessagesHistory)
NLOHMANN_JSON_TO(saveForBots)
NLOHMANN_JSON_TO(hideFromBlocked)
NLOHMANN_JSON_TO(disableAds)
NLOHMANN_JSON_TO(disableStories)
NLOHMANN_JSON_TO(disableCustomBackgrounds)
NLOHMANN_JSON_TO(showOnlyAddedEmojisAndStickers)
NLOHMANN_JSON_TO(collapseSimilarChannels)
NLOHMANN_JSON_TO(hideSimilarChannels)
NLOHMANN_JSON_TO(wideMultiplier)
NLOHMANN_JSON_TO(spoofWebviewAsAndroid)
NLOHMANN_JSON_TO(increaseWebviewHeight)
NLOHMANN_JSON_TO(increaseWebviewWidth)
NLOHMANN_JSON_TO(disableNotificationsDelay)
NLOHMANN_JSON_TO(localPremium)
NLOHMANN_JSON_TO(appIcon)
NLOHMANN_JSON_TO(simpleQuotesAndReplies)
NLOHMANN_JSON_TO(replaceBottomInfoWithIcons)
NLOHMANN_JSON_TO(deletedMark)
NLOHMANN_JSON_TO(editedMark)
NLOHMANN_JSON_TO(recentStickersCount)
NLOHMANN_JSON_TO(showReactionsPanelInContextMenu)
NLOHMANN_JSON_TO(showViewsPanelInContextMenu)
NLOHMANN_JSON_TO(showHideMessageInContextMenu)
NLOHMANN_JSON_TO(showUserMessagesInContextMenu)
NLOHMANN_JSON_TO(showMessageDetailsInContextMenu)
NLOHMANN_JSON_TO(showAttachButtonInMessageField)
NLOHMANN_JSON_TO(showCommandsButtonInMessageField)
NLOHMANN_JSON_TO(showEmojiButtonInMessageField)
NLOHMANN_JSON_TO(showMicrophoneButtonInMessageField)
NLOHMANN_JSON_TO(showAutoDeleteButtonInMessageField)
NLOHMANN_JSON_TO(showAttachPopup)
NLOHMANN_JSON_TO(showEmojiPopup)
NLOHMANN_JSON_TO(showLReadToggleInDrawer)
NLOHMANN_JSON_TO(showSReadToggleInDrawer)
NLOHMANN_JSON_TO(showGhostToggleInDrawer)
NLOHMANN_JSON_TO(showStreamerToggleInDrawer)
NLOHMANN_JSON_TO(showGhostToggleInTray)
NLOHMANN_JSON_TO(showStreamerToggleInTray)
NLOHMANN_JSON_TO(monoFont)
NLOHMANN_JSON_TO(showChannelReactions)
NLOHMANN_JSON_TO(showGroupReactions)
NLOHMANN_JSON_TO(hideNotificationCounters)
NLOHMANN_JSON_TO(hideNotificationBadge)
NLOHMANN_JSON_TO(hideAllChatsFolder)
NLOHMANN_JSON_TO(channelBottomButton)
NLOHMANN_JSON_TO(showPeerId)
NLOHMANN_JSON_TO(showMessageSeconds)
NLOHMANN_JSON_TO(showMessageShot)
NLOHMANN_JSON_TO(stickerConfirmation)
NLOHMANN_JSON_TO(gifConfirmation)
NLOHMANN_JSON_TO(voiceConfirmation)
}
inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nlohmann_json_t) {
const AyuGramSettings nlohmann_json_default_obj{};
NLOHMANN_JSON_FROM_WITH_DEFAULT(sendReadMessages)
NLOHMANN_JSON_FROM_WITH_DEFAULT(sendReadStories)
NLOHMANN_JSON_FROM_WITH_DEFAULT(sendOnlinePackets)
NLOHMANN_JSON_FROM_WITH_DEFAULT(sendUploadProgress)
NLOHMANN_JSON_FROM_WITH_DEFAULT(sendOfflinePacketAfterOnline)
NLOHMANN_JSON_FROM_WITH_DEFAULT(markReadAfterAction)
NLOHMANN_JSON_FROM_WITH_DEFAULT(useScheduledMessages)
NLOHMANN_JSON_FROM_WITH_DEFAULT(sendWithoutSound)
NLOHMANN_JSON_FROM_WITH_DEFAULT(saveDeletedMessages)
NLOHMANN_JSON_FROM_WITH_DEFAULT(saveMessagesHistory)
NLOHMANN_JSON_FROM_WITH_DEFAULT(saveForBots)
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideFromBlocked)
NLOHMANN_JSON_FROM_WITH_DEFAULT(disableAds)
NLOHMANN_JSON_FROM_WITH_DEFAULT(disableStories)
NLOHMANN_JSON_FROM_WITH_DEFAULT(disableCustomBackgrounds)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showOnlyAddedEmojisAndStickers)
NLOHMANN_JSON_FROM_WITH_DEFAULT(collapseSimilarChannels)
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideSimilarChannels)
NLOHMANN_JSON_FROM_WITH_DEFAULT(wideMultiplier)
NLOHMANN_JSON_FROM_WITH_DEFAULT(spoofWebviewAsAndroid)
NLOHMANN_JSON_FROM_WITH_DEFAULT(increaseWebviewHeight)
NLOHMANN_JSON_FROM_WITH_DEFAULT(increaseWebviewWidth)
NLOHMANN_JSON_FROM_WITH_DEFAULT(disableNotificationsDelay)
NLOHMANN_JSON_FROM_WITH_DEFAULT(localPremium)
NLOHMANN_JSON_FROM_WITH_DEFAULT(appIcon)
NLOHMANN_JSON_FROM_WITH_DEFAULT(simpleQuotesAndReplies)
NLOHMANN_JSON_FROM_WITH_DEFAULT(replaceBottomInfoWithIcons)
NLOHMANN_JSON_FROM_WITH_DEFAULT(deletedMark)
NLOHMANN_JSON_FROM_WITH_DEFAULT(editedMark)
NLOHMANN_JSON_FROM_WITH_DEFAULT(recentStickersCount)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showReactionsPanelInContextMenu)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showViewsPanelInContextMenu)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showHideMessageInContextMenu)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showUserMessagesInContextMenu)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showMessageDetailsInContextMenu)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showAttachButtonInMessageField)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showCommandsButtonInMessageField)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showEmojiButtonInMessageField)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showMicrophoneButtonInMessageField)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showAutoDeleteButtonInMessageField)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showAttachPopup)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showEmojiPopup)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showLReadToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showSReadToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGhostToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showStreamerToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGhostToggleInTray)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showStreamerToggleInTray)
NLOHMANN_JSON_FROM_WITH_DEFAULT(monoFont)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showChannelReactions)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGroupReactions)
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationCounters)
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationBadge)
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideAllChatsFolder)
NLOHMANN_JSON_FROM_WITH_DEFAULT(channelBottomButton)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showPeerId)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showMessageSeconds)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showMessageShot)
NLOHMANN_JSON_FROM_WITH_DEFAULT(stickerConfirmation)
NLOHMANN_JSON_FROM_WITH_DEFAULT(gifConfirmation)
NLOHMANN_JSON_FROM_WITH_DEFAULT(voiceConfirmation)
}
AyuGramSettings &getInstance();

View file

@ -42,7 +42,7 @@ void runOnce() {
lateInit();
}
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!settings.sendOfflinePacketAfterOnline) {
return;
}

View file

@ -0,0 +1,435 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "ayu_forward.h"
#include "apiwrap.h"
#include "ayu_sync.h"
#include "lang_auto.h"
#include "ayu/utils/telegram_helpers.h"
#include "base/random.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "history/history_item.h"
#include "storage/file_download.h"
#include "storage/localimageloader.h"
#include "storage/storage_account.h"
#include "storage/storage_media_prepare.h"
#include "styles/style_boxes.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/text/text_utilities.h"
namespace AyuForward {
std::unordered_map<PeerId, std::shared_ptr<ForwardState>> forwardStates;
bool isForwarding(const PeerId &id) {
const auto fwState = forwardStates.find(id);
if (id.value && fwState != forwardStates.end()) {
const auto state = *fwState->second;
return state.state != ForwardState::State::Finished
&& state.currentChunk < state.totalChunks
&& !state.stopRequested
&& state.totalChunks
&& state.totalMessages;
}
return false;
}
void cancelForward(const PeerId &id, const Main::Session &session) {
const auto fwState = forwardStates.find(id);
if (fwState != forwardStates.end()) {
fwState->second->stopRequested = true;
fwState->second->updateBottomBar(session, &id, ForwardState::State::Finished);
}
}
std::pair<QString, QString> stateName(const PeerId &id) {
const auto fwState = forwardStates.find(id);
if (fwState == forwardStates.end()) {
return std::make_pair(QString(), QString());
}
const auto state = fwState->second;
QString messagesString = tr::ayu_AyuForwardStatusSentCount(tr::now,
lt_count1,
QString::number(state->sentMessages),
lt_count2,
QString::number(state->totalMessages)
);
QString chunkString = tr::ayu_AyuForwardStatusChunkCount(tr::now,
lt_count1,
QString::number(state->currentChunk + 1),
lt_count2,
QString::number(state->totalChunks)
);
const auto partString = state->totalChunks <= 1 ? messagesString : (messagesString + "" + chunkString);
QString status;
if (state->state == ForwardState::State::Preparing) {
status = tr::ayu_AyuForwardStatusPreparing(tr::now);
} else if (state->state == ForwardState::State::Downloading) {
status = tr::ayu_AyuForwardStatusLoadingMedia(tr::now);
} else if (state->state == ForwardState::State::Sending) {
status = tr::ayu_AyuForwardStatusForwarding(tr::now);
} else {
// ForwardState::State::Finished
status = tr::ayu_AyuForwardStatusFinished(tr::now);
}
return std::make_pair(status, partString);
}
void ForwardState::updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st) {
state = st;
auto peerCopy = *peer;
crl::on_main([&, peerCopy]
{
session.changes().peerUpdated(session.data().peer(peerCopy), Data::PeerUpdate::Flag::Rights);
});
}
static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
const std::vector<not_null<HistoryItem*>> &items,
int &i,
std::vector<not_null<Data::Media*>> &groupMedia) {
const auto prepare = [&](not_null<Data::Media*> media)
{
groupMedia.emplace_back(media);
auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media));
if (prepared.path.isEmpty()) {
// otherwise will fail assertion in PrepareDetails
return prepared;
}
Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, PhotoSideLimit());
return prepared;
};
const auto startItem = items[i];
const auto media = startItem->media();
const auto groupId = startItem->groupId();
Ui::PreparedList list;
if (auto prepared = prepare(media); !prepared.path.isEmpty()) {
list.files.emplace_back(std::move(prepared));
}
if (!groupId.value) {
return list;
}
for (int k = i + 1; k < items.size(); ++k) {
const auto nextItem = items[k];
if (nextItem->groupId() != groupId) {
break;
}
if (const auto nextMedia = nextItem->media()) {
if (auto prepared = prepare(nextMedia); !prepared.path.isEmpty()) {
list.files.emplace_back(std::move(prepared));
}
i = k;
}
}
return list;
}
void sendMedia(
not_null<Main::Session*> session,
const std::shared_ptr<Ui::PreparedBundle> &bundle,
not_null<Data::Media*> primaryMedia,
Api::MessageToSend &&message,
bool sendImagesAsPhotos) {
if (const auto document = primaryMedia->document(); document && document->sticker()) {
AyuSync::sendStickerSync(session, message, document);
return;
}
auto mediaType = [&]
{
if (const auto document = primaryMedia->document()) {
if (document->isVoiceMessage()) {
return SendMediaType::Audio;
} else if (document->isVideoMessage()) {
return SendMediaType::Round;
}
return SendMediaType::File;
}
return SendMediaType::Photo;
}();
if (mediaType == SendMediaType::Round || mediaType == SendMediaType::Audio) {
const auto path = bundle->groups.front().list.files.front().path;
QFile file(path);
auto failed = false;
if (!file.open(QIODevice::ReadOnly)) {
LOG(("failed to open file for forward with reason: %1").arg(file.errorString()));
failed = true;
}
auto data = file.readAll();
if (!failed && data.size()) {
file.close();
AyuSync::sendVoiceSync(session,
data,
primaryMedia->document()->duration(),
mediaType == SendMediaType::Round,
message.action);
return;
}
// at least try to send it as squared-video
}
// workaround for media albums consisting of video and photos
if (sendImagesAsPhotos) {
mediaType = SendMediaType::Photo;
}
for (auto &group : bundle->groups) {
AyuSync::sendDocumentSync(
session,
group,
mediaType,
std::move(message.textWithTags),
message.action);
}
}
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem*>> &items) {
const auto needAyuForward = [&](const auto &item)
{
return isAyuForwardNeeded(item);
};
return std::ranges::any_of(items, needAyuForward);
}
bool isAyuForwardNeeded(not_null<HistoryItem*> item) {
if (item->isDeleted() || item->isAyuNoForwards() || item->ttlDestroyAt()) {
return true;
}
return false;
}
bool isFullAyuForwardNeeded(not_null<HistoryItem*> item) {
return item->from()->isAyuNoForwards() || item->history()->peer->isAyuNoForwards();
}
struct ForwardChunk
{
bool isAyuForwardNeeded;
std::vector<not_null<HistoryItem*>> items;
};
void intelligentForward(
not_null<Main::Session*> session,
const Api::SendAction &action,
const Data::ResolvedForwardDraft &draft) {
const auto history = action.history;
crl::on_main([&]
{
history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {});
});
const auto items = draft.items;
const auto peer = history->peer;
auto chunks = std::vector<ForwardChunk>();
auto currentArray = std::vector<not_null<HistoryItem*>>();
auto currentChunk = ForwardChunk({
.isAyuForwardNeeded = isAyuForwardNeeded(items[0]),
.items = currentArray
});
for (const auto &item : items) {
if (isAyuForwardNeeded(item) != currentChunk.isAyuForwardNeeded) {
currentChunk.items = currentArray;
chunks.push_back(currentChunk);
currentArray = std::vector<not_null<HistoryItem*>>();
currentChunk = ForwardChunk({
.isAyuForwardNeeded = isAyuForwardNeeded(item),
.items = currentArray
});
}
currentArray.push_back(item);
}
currentChunk.items = currentArray;
chunks.push_back(currentChunk);
auto state = std::make_shared<ForwardState>(chunks.size());
forwardStates[peer->id] = state;
for (const auto &chunk : chunks) {
if (chunk.isAyuForwardNeeded) {
forwardMessages(session, action, true, Data::ResolvedForwardDraft(chunk.items));
} else {
state->totalMessages = chunk.items.size();
state->sentMessages = 0;
state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
AyuSync::forwardMessagesSync(session, chunk.items, action, draft.options);
state->sentMessages = state->totalMessages;
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
}
state->currentChunk++;
}
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
}
void forwardMessages(
not_null<Main::Session*> session,
const Api::SendAction &action,
bool forwardState,
const Data::ResolvedForwardDraft &draft) {
const auto items = draft.items;
const auto history = action.history;
const auto peer = history->peer;
crl::on_main([&]
{
history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {});
});
std::shared_ptr<ForwardState> state;
if (forwardState) {
state = std::make_shared<ForwardState>(*forwardStates[peer->id]);
} else {
state = std::make_shared<ForwardState>(1);
}
forwardStates[peer->id] = state;
std::unordered_map<uint64, uint64> groupIds;
std::vector<not_null<HistoryItem*>> toBeDownloaded;
for (const auto item : items) {
if (mediaDownloadable(item->media())) {
toBeDownloaded.push_back(item);
}
if (item->groupId()) {
const auto currentId = groupIds.find(item->groupId().value);
if (currentId == groupIds.end()) {
groupIds[item->groupId().value] = base::RandomValue<uint64>();
}
}
}
state->totalMessages = items.size();
if (!toBeDownloaded.empty()) {
state->state = ForwardState::State::Downloading;
state->updateBottomBar(*session, &peer->id, ForwardState::State::Downloading);
AyuSync::loadDocuments(session, toBeDownloaded);
}
state->sentMessages = 0;
state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
for (int i = 0; i < items.size(); i++) {
const auto item = items[i];
if (state->stopRequested) {
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
return;
}
auto extractedText = extractText(item);
if (extractedText.empty() && !mediaDownloadable(item->media())) {
continue;
}
auto message = Api::MessageToSend(Api::SendAction(session->data().history(peer->id)));
message.action.replyTo = action.replyTo;
if (draft.options != Data::ForwardOptions::NoNamesAndCaptions) {
message.textWithTags = extractedText;
}
if (!mediaDownloadable(item->media())) {
AyuSync::sendMessageSync(session, message);
} else if (const auto media = item->media()) {
if (media->poll()) {
AyuSync::sendMessageSync(session, message);
continue;
}
std::vector<not_null<Data::Media*>> groupMedia;
auto preparedMedia = prepareMedia(session, items, i, groupMedia);
Ui::SendFilesWay way;
way.setGroupFiles(true);
way.setSendImagesAsPhotos(false);
for (const auto &media2 : groupMedia) {
if (media2->photo()) {
way.setSendImagesAsPhotos(true);
break;
}
}
// remove not finished files
for (int j = preparedMedia.files.size() - 1; j >= 0; j--) {
auto &file = preparedMedia.files[j];
QFile f(file.path);
if (groupMedia[j]->photo() && f.size() < groupMedia[j]->photo()->imageByteSize(Data::PhotoSize::Large)
||
groupMedia[j]->document() && f.size() < groupMedia[j]->document()->size
) {
preparedMedia.files.erase(preparedMedia.files.begin() + j);
}
}
if (preparedMedia.files.empty()) {
continue;
}
auto groups = Ui::DivideByGroups(
std::move(preparedMedia),
way,
peer->slowmodeApplied());
auto bundle = Ui::PrepareFilesBundle(
std::move(groups),
way,
message.textWithTags,
false);
sendMedia(session, bundle, media, std::move(message), way.sendImagesAsPhotos());
}
// if there are grouped messages
// "i" is incremented in prepareMedia
state->sentMessages = i + 1;
state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
}
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
}
} // namespace AyuFeatures::AyuForward

View file

@ -0,0 +1,52 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "history/history.h"
#include "main/main_session.h"
namespace AyuForward {
bool isForwarding(const PeerId &id);
void cancelForward(const PeerId &id, const Main::Session &session);
std::pair<QString, QString> stateName(const PeerId &id);
class ForwardState
{
public:
enum class State
{
Preparing,
Downloading,
Sending,
Finished
};
void updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st);
int totalChunks;
int currentChunk;
int totalMessages;
int sentMessages;
State state = State::Preparing;
bool stopRequested = false;
};
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem*>> &items);
bool isAyuForwardNeeded(not_null<HistoryItem*> item);
bool isFullAyuForwardNeeded(not_null<HistoryItem*> item);
void intelligentForward(
not_null<Main::Session*> session,
const Api::SendAction &action,
const Data::ResolvedForwardDraft &draft);
void forwardMessages(
not_null<Main::Session*> session,
const Api::SendAction &action,
bool forwardState,
const Data::ResolvedForwardDraft &draft);
}

View file

@ -0,0 +1,333 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "ayu_sync.h"
#include "api/api_sending.h"
#include "apiwrap.h"
#include "ayu/utils/telegram_helpers.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/file_utilities.h"
#include "data/data_document.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
#include "storage/file_download_mtproto.h"
#include "storage/localimageloader.h"
class TimedCountDownLatch
{
public:
explicit TimedCountDownLatch(int count)
: count_(count) {
}
void countDown() {
std::unique_lock lock(mutex_);
if (count_ > 0) {
count_--;
}
if (count_ == 0) {
cv_.notify_all();
}
}
bool await(std::chrono::milliseconds timeout) {
std::unique_lock lock(mutex_);
if (count_ == 0) {
return true;
}
return cv_.wait_for(lock, timeout, [this] { return count_ == 0; });
}
private:
std::mutex mutex_;
std::condition_variable cv_;
int count_;
};
namespace AyuSync {
QString pathForSave(not_null<Main::Session*> session) {
auto path = Core::App().settings().downloadPath();
if (path.isEmpty()) {
return File::DefaultDownloadPath(session);
}
if (path == FileDialog::Tmp()) {
return session->local().tempDirectory();
}
return path;
}
QString filePath(not_null<Main::Session*> session, const Data::Media *media) {
if (!media) {
return {};
}
if (const auto document = media->document()) {
if (!document->filename().isEmpty()) {
return pathForSave(session) + media->document()->filename();
}
if (const auto name = document->filepath(true); !name.isEmpty()) {
return name;
}
if (document->isVoiceMessage()) {
return pathForSave(session) + "audio_" + QString::number(document->getDC()) + "_" +
QString::number(document->id) + ".ogg";
}
if (document->isVideoMessage()) {
return pathForSave(session) + "round_" + QString::number(document->getDC()) + "_" +
QString::number(document->id) + ".mp4";
}
} else if (const auto photo = media->photo()) {
return pathForSave(session) + QString::number(photo->getDC()) + "_" + QString::number(photo->id) + ".jpg";
}
return {};
}
qint64 fileSize(not_null<HistoryItem*> item) {
if (const auto path = filePath(&item->history()->session(), item->media()); !path.isEmpty()) {
QFile file(path);
if (file.exists()) {
auto size = file.size();
return size;
}
}
return 0;
}
void loadDocuments(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items) {
for (const auto &item : items) {
if (const auto data = item->media()->document()) {
const auto size = fileSize(item);
if (size == data->size) {
continue;
}
if (size && size < data->size) {
// in case there some unfinished file
QFile file(filePath(session, item->media()));
file.remove();
}
loadDocumentSync(session, data, item);
} else if (auto photo = item->media()->photo()) {
if (fileSize(item) == photo->imageByteSize(Data::PhotoSize::Large)) {
continue;
}
loadPhotoSync(session, std::pair(photo, item->fullId()));
}
}
}
void loadDocumentSync(not_null<Main::Session*> session, DocumentData *data, not_null<HistoryItem*> item) {
auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>();
auto path = filePath(session, item->media());
if (path.isEmpty()) {
return;
}
crl::on_main([&]
{
data->save(Data::FileOriginMessage(item->fullId()), path);
rpl::single() | rpl::then(
session->downloaderTaskFinished()
) | rpl::filter([&]
{
return data->status == FileDownloadFailed || fileSize(item) == data->size;
}) | rpl::start_with_next([&]() mutable
{
latch->countDown();
},
*lifetime);
});
constexpr auto overall = std::chrono::minutes(15);
const auto startTime = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - startTime < overall) {
if (latch->await(std::chrono::minutes(5))) {
break;
}
if (!data->loading()) {
break;
}
}
base::take(lifetime)->destroy();
}
void forwardMessagesSync(not_null<Main::Session*> session,
const std::vector<not_null<HistoryItem*>> &items,
const ApiWrap::SendAction &action,
Data::ForwardOptions options) {
auto latch = std::make_shared<TimedCountDownLatch>(1);
crl::on_main([=, &latch]
{
session->api().forwardMessages(Data::ResolvedForwardDraft(items, options),
action,
[&]
{
latch->countDown();
});
});
latch->await(std::chrono::minutes(1));
}
void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<PhotoData*>, FullMsgId> &photo) {
const auto folderPath = pathForSave(session);
const auto downloadPath = folderPath.isEmpty() ? Core::App().settings().downloadPath() : folderPath;
const auto path = downloadPath.isEmpty()
? File::DefaultDownloadPath(session)
: downloadPath == FileDialog::Tmp()
? session->local().tempDirectory()
: downloadPath;
if (path.isEmpty()) {
return;
}
if (!QDir().mkpath(path)) {
return;
}
const auto view = photo.first->createMediaView();
if (!view) {
return;
}
view->wanted(Data::PhotoSize::Large, photo.second);
const auto finalCheck = [=]
{
return !photo.first->loading();
};
const auto saveToFiles = [=]
{
QDir directory(path);
const auto dir = directory.absolutePath();
const auto nameBase = dir.endsWith('/') ? dir : dir + '/';
const auto fullPath = nameBase + QString::number(photo.first->getDC()) + "_" + QString::number(photo.first->id)
+ ".jpg";
view->saveToFile(fullPath);
};
auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>();
if (finalCheck()) {
saveToFiles();
} else {
crl::on_main([&]
{
session->downloaderTaskFinished() | rpl::filter([&]
{
return finalCheck();
}) | rpl::start_with_next([&]() mutable
{
saveToFiles();
latch->countDown();
},
*lifetime);
});
latch->await(std::chrono::minutes(5));
base::take(lifetime)->destroy();
}
}
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message) {
crl::on_main([=, &message]
{
// we cannot send events to objects
// owned by a different thread
// because sendMessage updates UI too
session->api().sendMessage(std::move(message));
});
waitForMsgSync(session, message.action);
}
void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action) {
auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>();
crl::on_main([&]
{
session->data().itemIdChanged()
| rpl::filter([&](const Data::Session::IdChange &update)
{
return action.history->peer->id == update.newId.peer;
}) | rpl::start_with_next([&]
{
latch->countDown();
},
*lifetime);
});
latch->await(std::chrono::minutes(5));
base::take(lifetime)->destroy();
}
void sendDocumentSync(not_null<Main::Session*> session,
Ui::PreparedGroup &group,
SendMediaType type,
TextWithTags &&caption,
const Api::SendAction &action) {
auto groupId = std::make_shared<SendingAlbum>();
groupId->groupId = base::RandomValue<uint64>();
crl::on_main([=, lst = std::move(group.list), caption = std::move(caption)]() mutable
{
session->api().sendFiles(std::move(lst), type, std::move(caption), groupId, action);
});
waitForMsgSync(session, action);
}
void sendStickerSync(not_null<Main::Session*> session,
Api::MessageToSend &message,
not_null<DocumentData*> document) {
auto &action = message.action;
crl::on_main([&]
{
Api::SendExistingDocument(std::move(message), document, std::nullopt);
});
waitForMsgSync(session, action);
}
void sendVoiceSync(not_null<Main::Session*> session,
const QByteArray &data,
int64_t duration,
bool video,
const Api::SendAction &action) {
crl::on_main([&]
{
session->api().sendVoiceMessage(data,
QVector<signed char>(),
duration,
video,
action);
});
waitForMsgSync(session, action);
}
} // namespace AyuSync

View file

@ -0,0 +1,49 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "apiwrap.h"
#include "base/random.h"
#include "data/data_document.h"
#include "data/data_media_types.h"
#include "data/data_photo.h"
#include "history/history_item.h"
#include "storage/file_download.h"
#include "storage/file_upload.h"
#include "storage/storage_account.h"
#include "ui/chat/attach/attach_prepare.h"
namespace AyuSync {
QString pathForSave(not_null<Main::Session*> session);
QString filePath(not_null<Main::Session*> session, const Data::Media *media);
void loadDocuments(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items);
bool isMediaDownloadable(Data::Media *media);
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message);
void sendDocumentSync(not_null<Main::Session*> session,
Ui::PreparedGroup &group,
SendMediaType type,
TextWithTags &&caption,
const Api::SendAction &action);
void sendStickerSync(not_null<Main::Session*> session,
Api::MessageToSend &message,
not_null<DocumentData*> document);
void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action);
void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<PhotoData*>, FullMsgId> &photos);
void loadDocumentSync(not_null<Main::Session*> session, DocumentData *data, not_null<HistoryItem*> item);
void forwardMessagesSync(not_null<Main::Session*> session,
const std::vector<not_null<HistoryItem*>> &items,
const ApiWrap::SendAction &action,
Data::ForwardOptions options);
void sendVoiceSync(not_null<Main::Session*> session,
const QByteArray &data,
int64_t duration,
bool video,
const Api::SendAction &action);
}

View file

@ -161,7 +161,7 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
HistoryView::Context elementContext() override;
bool elementHideReply(not_null<const HistoryView::Element*> view) override;
bool elementIsChatWide() override;
HistoryView::ElementChatMode elementChatMode() override;
private:
const not_null<QWidget*> _parent;
@ -207,8 +207,9 @@ bool MessageShotDelegate::elementHideReply(not_null<const HistoryView::Element*>
return false;
}
bool MessageShotDelegate::elementIsChatWide() {
return true;
HistoryView::ElementChatMode MessageShotDelegate::elementChatMode() {
using Mode = HistoryView::ElementChatMode;
return Mode::Wide;
}
QImage removeEmptySpaceAround(const QImage &original) {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -133,7 +133,7 @@ extern "C" {
**
** Since [version 3.6.18] ([dateof:3.6.18]),
** SQLite source code has been stored in the
** <a href="http://www.fossil-scm.org/">Fossil configuration management
** <a href="http://fossil-scm.org/">Fossil configuration management
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID
@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.49.1"
#define SQLITE_VERSION_NUMBER 3049001
#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70"
#define SQLITE_VERSION "3.50.2"
#define SQLITE_VERSION_NUMBER 3050002
#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -1163,6 +1163,12 @@ struct sqlite3_io_methods {
** the value that M is to be set to. Before returning, the 32-bit signed
** integer is overwritten with the previous value of M.
**
** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]]
** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the
** VFS to block when taking a SHARED lock to connect to a wal mode database.
** This is used to implement the functionality associated with
** SQLITE_SETLK_BLOCK_ON_CONNECT.
**
** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
** a database file. The argument is a pointer to a 32-bit unsigned integer.
@ -1259,6 +1265,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKSM_FILE 41
#define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@ -1989,13 +1996,16 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
** the default size of lookaside memory on each [database connection].
** the default size of [lookaside memory] on each [database connection].
** The first argument is the
** size of each lookaside buffer slot and the second is the number of
** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
** option to [sqlite3_db_config()] can be used to change the lookaside
** configuration on individual connections.)^ </dd>
** size of each lookaside buffer slot ("sz") and the second is the number of
** slots allocated to each database connection ("cnt").)^
** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size.
** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can
** be used to change the lookaside configuration on individual connections.)^
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
** default lookaside configuration at compile-time.
** </dd>
**
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
@ -2232,31 +2242,50 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
** configuration of the lookaside memory allocator within a database
** configuration of the [lookaside memory allocator] within a database
** connection.
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
** in the [DBCONFIG arguments|usual format].
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
** should have a total of five parameters.
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
** <ol>
** <li><p>The first argument ("buf") is a
** pointer to a memory buffer to use for lookaside memory.
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
** may be NULL in which case SQLite will allocate the
** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
** size of each lookaside buffer slot. ^The third argument is the number of
** slots. The size of the buffer in the first argument must be greater than
** or equal to the product of the second and third arguments. The buffer
** must be aligned to an 8-byte boundary. ^If the second argument to
** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally
** rounded down to the next smaller multiple of 8. ^(The lookaside memory
** The first argument may be NULL in which case SQLite will allocate the
** lookaside buffer itself using [sqlite3_malloc()].
** <li><P>The second argument ("sz") is the
** size of each lookaside buffer slot. Lookaside is disabled if "sz"
** is less than 8. The "sz" argument should be a multiple of 8 less than
** 65536. If "sz" does not meet this constraint, it is reduced in size until
** it does.
** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled
** if "cnt"is less than 1. The "cnt" value will be reduced, if necessary, so
** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt"
** parameter is usually chosen so that the product of "sz" and "cnt" is less
** than 1,000,000.
** </ol>
** <p>If the "buf" argument is not NULL, then it must
** point to a memory buffer with a size that is greater than
** or equal to the product of "sz" and "cnt".
** The buffer must be aligned to an 8-byte boundary.
** The lookaside memory
** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero.
** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd>
** [SQLITE_BUSY].
** If the "buf" argument is NULL and an attempt
** to allocate memory based on "sz" and "cnt" fails, then
** lookaside is silently disabled.
** <p>
** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the
** default lookaside configuration at initialization. The
** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside
** configuration at compile-time. Typical values for lookaside are 1200 for
** "sz" and 40 to 100 for "cnt".
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
@ -2993,6 +3022,44 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
*/
SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
/*
** CAPI3REF: Set the Setlk Timeout
** METHOD: sqlite3
**
** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If
** the VFS supports blocking locks, it sets the timeout in ms used by
** eligible locks taken on wal mode databases by the specified database
** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does
** not support blocking locks, this function is a no-op.
**
** Passing 0 to this function disables blocking locks altogether. Passing
** -1 to this function requests that the VFS blocks for a long time -
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
** Internally, each SQLite database handle store two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
** values, this function sets only the setlk-timeout value. Therefore,
** to configure separate busy-timeout and setlk-timeout values for a single
** database handle, call sqlite3_busy_timeout() followed by this function.
**
** Whenever the number of connections to a wal mode database falls from
** 1 to 0, the last connection takes an exclusive lock on the database,
** then checkpoints and deletes the wal file. While it is doing this, any
** new connection that tries to read from the database fails with an
** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is
** passed to this API, the new connection blocks until the exclusive lock
** has been released.
*/
SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
/*
** CAPI3REF: Flags for sqlite3_setlk_timeout()
*/
#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01
/*
** CAPI3REF: Convenience Routines For Running Queries
** METHOD: sqlite3
@ -4012,7 +4079,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
**
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
** database filename D with corresponding journal file J and WAL file W and
** with N URI parameters key/values pairs in the array P. The result from
** an array P of N URI Key/Value pairs. The result from
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
** is safe to pass to routines like:
** <ul>
@ -4693,7 +4760,7 @@ typedef struct sqlite3_context sqlite3_context;
** METHOD: sqlite3_stmt
**
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
** literals may be replaced by a [parameter] that matches one of following
** literals may be replaced by a [parameter] that matches one of the following
** templates:
**
** <ul>
@ -4738,7 +4805,7 @@ typedef struct sqlite3_context sqlite3_context;
**
** [[byte-order determination rules]] ^The byte-order of
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
** found in first character, which is removed, or in the absence of a BOM
** found in the first character, which is removed, or in the absence of a BOM
** the byte order is the native byte order of the host
** machine for sqlite3_bind_text16() or the byte order specified in
** the 6th parameter for sqlite3_bind_text64().)^
@ -4758,7 +4825,7 @@ typedef struct sqlite3_context sqlite3_context;
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
** that parameter must be the byte offset
** where the NUL terminator would occur assuming the string were NUL
** terminated. If any NUL characters occurs at byte offsets less than
** terminated. If any NUL characters occur at byte offsets less than
** the value of the fourth parameter then the resulting string value will
** contain embedded NULs. The result of expressions involving strings
** with embedded NULs is undefined.
@ -4970,7 +5037,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
** METHOD: sqlite3_stmt
**
** ^These routines provide a means to determine the database, table, and
** table column that is the origin of a particular result column in
** table column that is the origin of a particular result column in a
** [SELECT] statement.
** ^The name of the database or table or column can be returned as
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
@ -5108,7 +5175,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
** other than [SQLITE_ROW] before any subsequent invocation of
** sqlite3_step(). Failure to reset the prepared statement using
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]),
** sqlite3_step() began
** calling [sqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
@ -5539,8 +5606,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
**
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
** all application-defined SQL functions that do not need to be
** used inside of triggers, view, CHECK constraints, or other elements of
** the database schema. This flags is especially recommended for SQL
** used inside of triggers, views, CHECK constraints, or other elements of
** the database schema. This flag is especially recommended for SQL
** functions that have side effects or reveal internal application state.
** Without this flag, an attacker might be able to modify the schema of
** a database file to include invocations of the function with parameters
@ -5571,7 +5638,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** [user-defined window functions|available here].
**
** ^(If the final parameter to sqlite3_create_function_v2() or
** sqlite3_create_window_function() is not NULL, then it is destructor for
** sqlite3_create_window_function() is not NULL, then it is the destructor for
** the application data pointer. The destructor is invoked when the function
** is deleted, either by being overloaded or when the database connection
** closes.)^ ^The destructor is also invoked if the call to
@ -5971,7 +6038,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
** METHOD: sqlite3_value
**
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
** object V and returns a pointer to that copy. ^The [sqlite3_value] returned
** is a [protected sqlite3_value] object even if the input is not.
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
** memory allocation fails. ^If V is a [pointer value], then the result
@ -6009,7 +6076,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
** allocation error occurs.
**
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
** determined by the N parameter on first successful call. Changing the
** determined by the N parameter on the first successful call. Changing the
** value of N in any subsequent call to sqlite3_aggregate_context() within
** the same aggregate function instance will not resize the memory
** allocation.)^ Within the xFinal callback, it is customary to set
@ -6171,7 +6238,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
**
** Security Warning: These interfaces should not be exposed in scripting
** languages or in other circumstances where it might be possible for an
** an attacker to invoke them. Any agent that can invoke these interfaces
** attacker to invoke them. Any agent that can invoke these interfaces
** can probably also take control of the process.
**
** Database connection client data is only available for SQLite
@ -6285,7 +6352,7 @@ typedef void (*sqlite3_destructor_type)(void*);
** pointed to by the 2nd parameter are taken as the application-defined
** function result. If the 3rd parameter is non-negative, then it
** must be the byte offset into the string where the NUL terminator would
** appear if the string where NUL terminated. If any NUL characters occur
** appear if the string were NUL terminated. If any NUL characters occur
** in the string at a byte offset that is less than the value of the 3rd
** parameter, then the resulting string will contain embedded NULs and the
** result of expressions operating on strings with embedded NULs is undefined.
@ -6343,7 +6410,7 @@ typedef void (*sqlite3_destructor_type)(void*);
** string and preferably a string literal. The sqlite3_result_pointer()
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
**
** If these routines are called from within the different thread
** If these routines are called from within a different thread
** than the one containing the application-defined function that received
** the [sqlite3_context] pointer, the results are undefined.
*/
@ -6749,7 +6816,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
** METHOD: sqlite3
**
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
** for the N-th database on database connection D, or a NULL pointer of N is
** for the N-th database on database connection D, or a NULL pointer if N is
** out of range. An N value of 0 means the main database file. An N of 1 is
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
** databases.
@ -6844,7 +6911,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
** <dd>The SQLITE_TXN_READ state means that the database is currently
** in a read transaction. Content has been read from the database file
** but nothing in the database file has changed. The transaction state
** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are
** no other conflicting concurrent write transactions. The transaction
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
** [COMMIT].</dd>
@ -6853,7 +6920,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
** in a write transaction. Content has been written to the database file
** but has not yet committed. The transaction state will change to
** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
*/
#define SQLITE_TXN_NONE 0
#define SQLITE_TXN_READ 1
@ -7004,6 +7071,8 @@ SQLITE_API int sqlite3_autovacuum_pages(
**
** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table.
** ^The update hook is disabled by invoking sqlite3_update_hook()
** with a NULL pointer as the second parameter.
** ^The first argument to the callback is a copy of the third argument
** to sqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
@ -7132,7 +7201,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
** CAPI3REF: Impose A Limit On Heap Size
**
** These interfaces impose limits on the amount of heap memory that will be
** by all database connections within a single process.
** used by all database connections within a single process.
**
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
** soft limit on the amount of heap memory that may be allocated by SQLite.
@ -7190,7 +7259,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
** </ul>)^
**
** The circumstances under which SQLite will enforce the heap limits may
** changes in future releases of SQLite.
** change in future releases of SQLite.
*/
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
@ -7305,8 +7374,8 @@ SQLITE_API int sqlite3_table_column_metadata(
** ^The entry point is zProc.
** ^(zProc may be 0, in which case SQLite will try to come up with an
** entry point name on its own. It first tries "sqlite3_extension_init".
** If that does not work, it constructs a name "sqlite3_X_init" where the
** X is consists of the lower-case equivalent of all ASCII alphabetic
** If that does not work, it constructs a name "sqlite3_X_init" where
** X consists of the lower-case equivalent of all ASCII alphabetic
** characters in the filename from the last "/" to the first following
** "." and omitting any initial "lib".)^
** ^The sqlite3_load_extension() interface returns
@ -7377,7 +7446,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
** ^(Even though the function prototype shows that xEntryPoint() takes
** no arguments and returns void, SQLite invokes xEntryPoint() with three
** arguments and expects an integer result as if the signature of the
** entry point where as follows:
** entry point were as follows:
**
** <blockquote><pre>
** &nbsp; int xEntryPoint(
@ -7541,7 +7610,7 @@ struct sqlite3_module {
** virtual table and might not be checked again by the byte code.)^ ^(The
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
** is left in its default setting of false, the constraint will always be
** checked separately in byte code. If the omit flag is change to true, then
** checked separately in byte code. If the omit flag is changed to true, then
** the constraint may or may not be checked in byte code. In other words,
** when the omit flag is true there is no guarantee that the constraint will
** not be checked again using byte code.)^
@ -7567,7 +7636,7 @@ struct sqlite3_module {
** The xBestIndex method may optionally populate the idxFlags field with a
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
** output to show the idxNum has hex instead of as decimal. Another flag is
** output to show the idxNum as hex instead of as decimal. Another flag is
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
** return at most one row.
**
@ -7708,7 +7777,7 @@ struct sqlite3_index_info {
** the implementation of the [virtual table module]. ^The fourth
** parameter is an arbitrary client data pointer that is passed through
** into the [xCreate] and [xConnect] methods of the virtual table module
** when a new virtual table is be being created or reinitialized.
** when a new virtual table is being created or reinitialized.
**
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
** is a pointer to a destructor for the pClientData. ^SQLite will
@ -7873,7 +7942,7 @@ typedef struct sqlite3_blob sqlite3_blob;
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
** on *ppBlob after this function it returns.
** on *ppBlob after this function returns.
**
** This function fails with SQLITE_ERROR if any of the following are true:
** <ul>
@ -7993,7 +8062,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
**
** ^Returns the size in bytes of the BLOB accessible via the
** successfully opened [BLOB handle] in its only argument. ^The
** incremental blob I/O routines can only read or overwriting existing
** incremental blob I/O routines can only read or overwrite existing
** blob content; they cannot change the size of a blob.
**
** This routine only works on a [BLOB handle] which has been created
@ -8143,7 +8212,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** ^The sqlite3_mutex_alloc() routine allocates a new
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
** routine returns NULL if it is unable to allocate the requested
** mutex. The argument to sqlite3_mutex_alloc() must one of these
** mutex. The argument to sqlite3_mutex_alloc() must be one of these
** integer constants:
**
** <ul>
@ -8376,7 +8445,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
** CAPI3REF: Retrieve the mutex for a database connection
** METHOD: sqlite3
**
** ^This interface returns a pointer the [sqlite3_mutex] object that
** ^This interface returns a pointer to the [sqlite3_mutex] object that
** serializes access to the [database connection] given in the argument
** when the [threading mode] is Serialized.
** ^If the [threading mode] is Single-thread or Multi-thread then this
@ -8499,7 +8568,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
** CAPI3REF: SQL Keyword Checking
**
** These routines provide access to the set of SQL language keywords
** recognized by SQLite. Applications can uses these routines to determine
** recognized by SQLite. Applications can use these routines to determine
** whether or not a specific identifier needs to be escaped (for example,
** by enclosing in double-quotes) so as not to confuse the parser.
**
@ -8667,7 +8736,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*);
** content of the dynamic string under construction in X. The value
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
** and might be freed or altered by any subsequent method on the same
** [sqlite3_str] object. Applications must not used the pointer returned
** [sqlite3_str] object. Applications must not use the pointer returned by
** [sqlite3_str_value(X)] after any subsequent method call on the same
** object. ^Applications may change the content of the string returned
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
@ -8753,7 +8822,7 @@ SQLITE_API int sqlite3_status64(
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
** buffer and where forced to overflow to [sqlite3_malloc()]. The
** returned value includes allocations that overflowed because they
** where too large (they were larger than the "sz" parameter to
** were too large (they were larger than the "sz" parameter to
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
** no space was left in the page cache.</dd>)^
**
@ -8837,28 +8906,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
** <dd>This parameter returns the number of malloc attempts that were
** satisfied using lookaside memory. Only the high-water value is meaningful;
** the current value is always zero.)^
** the current value is always zero.</dd>)^
**
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
** <dd>This parameter returns the number malloc attempts that might have
** <dd>This parameter returns the number of malloc attempts that might have
** been satisfied using lookaside memory but failed due to the amount of
** memory requested being larger than the lookaside slot size.
** Only the high-water value is meaningful;
** the current value is always zero.)^
** the current value is always zero.</dd>)^
**
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
** <dd>This parameter returns the number malloc attempts that might have
** <dd>This parameter returns the number of malloc attempts that might have
** been satisfied using lookaside memory but failed due to all lookaside
** memory already being in use.
** Only the high-water value is meaningful;
** the current value is always zero.)^
** the current value is always zero.</dd>)^
**
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
** <dd>This parameter returns the approximate number of bytes of heap
** memory used by all pager caches associated with the database connection.)^
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
** </dd>
**
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
@ -8867,10 +8937,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** memory used by that pager cache is divided evenly between the attached
** connections.)^ In other words, if none of the pager caches associated
** with the database connection are shared, this request returns the same
** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are
** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are
** shared, the value returned by this call will be smaller than that returned
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.</dd>
**
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
** <dd>This parameter returns the approximate number of bytes of heap
@ -8880,6 +8950,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** schema memory is shared with other database connections due to
** [shared cache mode] being enabled.
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
** </dd>
**
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
** <dd>This parameter returns the approximate number of bytes of heap
@ -8916,7 +8987,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** been written to disk in the middle of a transaction due to the page
** cache overflowing. Transactions are more efficient if they are written
** to disk all at once. When pages spill mid-transaction, that introduces
** additional overhead. This parameter can be used help identify
** additional overhead. This parameter can be used to help identify
** inefficiencies that can be resolved by increasing the cache size.
** </dd>
**
@ -9396,7 +9467,7 @@ typedef struct sqlite3_backup sqlite3_backup;
** external process or via a database connection other than the one being
** used by the backup operation, then the backup will be automatically
** restarted by the next call to sqlite3_backup_step(). ^If the source
** database is modified by the using the same database connection as is used
** database is modified by using the same database connection as is used
** by the backup operation, then the backup database is automatically
** updated at the same time.
**
@ -9413,7 +9484,7 @@ typedef struct sqlite3_backup sqlite3_backup;
** and may not be used following a call to sqlite3_backup_finish().
**
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
** sqlite3_backup_step() errors occurred, regardless or whether or not
** sqlite3_backup_step() errors occurred, regardless of whether or not
** sqlite3_backup_step() completed.
** ^If an out-of-memory condition or IO error occurred during any prior
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
@ -10483,7 +10554,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
** METHOD: sqlite3
**
** ^If a write-transaction is open on [database connection] D when the
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty
** pages in the pager-cache that are not currently in use are written out
** to disk. A dirty page may be in use if a database cursor created by an
** active SQL statement is reading from it, or if it is page 1 of a database
@ -11486,9 +11557,10 @@ SQLITE_API void sqlite3session_table_filter(
** is inserted while a session object is enabled, then later deleted while
** the same session object is disabled, no INSERT record will appear in the
** changeset, even though the delete took place while the session was disabled.
** Or, if one field of a row is updated while a session is disabled, and
** another field of the same row is updated while the session is enabled, the
** resulting changeset will contain an UPDATE change that updates both fields.
** Or, if one field of a row is updated while a session is enabled, and
** then another field of the same row is updated while the session is disabled,
** the resulting changeset will contain an UPDATE change that updates both
** fields.
*/
SQLITE_API int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */
@ -11560,8 +11632,9 @@ SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession
** database zFrom the contents of the two compatible tables would be
** identical.
**
** It an error if database zFrom does not exist or does not contain the
** required compatible table.
** Unless the call to this function is a no-op as described above, it is an
** error if database zFrom does not exist or does not contain the required
** compatible table.
**
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
@ -11696,7 +11769,7 @@ SQLITE_API int sqlite3changeset_start_v2(
** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
**
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
** <dt>SQLITE_CHANGESETSTART_INVERT <dd>
** Invert the changeset while iterating through it. This is equivalent to
** inverting a changeset using sqlite3changeset_invert() before applying it.
** It is an error to specify this flag with a patchset.
@ -12011,19 +12084,6 @@ SQLITE_API int sqlite3changeset_concat(
void **ppOut /* OUT: Buffer containing output changeset */
);
/*
** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
*/
SQLITE_API int sqlite3changeset_upgrade(
sqlite3 *db,
const char *zDb,
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
/*
** CAPI3REF: Changegroup Handle
**

View file

@ -14,7 +14,7 @@ static QImage LAST_LOADED_NO_MARGIN;
namespace AyuAssets {
void loadAppIco() {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
QString appDataPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
QString tempIconPath = appDataPath + "/AyuGram.ico";
@ -30,7 +30,7 @@ void loadAppIco() {
}
void loadIcons() {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (LAST_LOADED_NAME != settings.appIcon) {
LAST_LOADED_NAME = settings.appIcon;
@ -43,7 +43,7 @@ void loadIcons() {
}
}
QImage loadPreview(QString name) {
QImage loadPreview(const QString& name) {
return QImage(qsl(":/gui/art/ayu/%1/app_preview.png").arg(name));
}

View file

@ -26,7 +26,7 @@ ICON(EXTERA2, "extera2");
void loadAppIco();
QImage loadPreview(QString name);
QImage loadPreview(const QString& name);
QString currentAppLogoName();
QImage currentAppLogo();

View file

@ -1,93 +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, 2025
#include "edit_edited_mark.h"
#include "boxes/peer_list_controllers.h"
#include "lang/lang_keys.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_widgets.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/special_fields.h"
#include "ayu/ayu_settings.h"
EditEditedMarkBox::EditEditedMarkBox(QWidget *)
: _text(
this,
st::defaultInputField,
tr::ayu_EditedMarkText(),
AyuSettings::getInstance().editedMark) {
}
void EditEditedMarkBox::prepare() {
const auto defaultEditedMark = tr::lng_edited(tr::now);
auto newHeight = st::contactPadding.top() + _text->height();
setTitle(tr::ayu_EditedMarkText());
newHeight += st::boxPadding.bottom() + st::contactPadding.bottom();
setDimensions(st::boxWidth, newHeight);
addLeftButton(tr::ayu_BoxActionReset(),
[=]
{
_text->setText(defaultEditedMark);
});
addButton(tr::lng_settings_save(),
[=]
{
save();
});
addButton(tr::lng_cancel(),
[=]
{
closeBox();
});
const auto submitted = [=]
{
submit();
};
_text->submits(
) | rpl::start_with_next(submitted, _text->lifetime());
}
void EditEditedMarkBox::setInnerFocus() {
_text->setFocusFast();
}
void EditEditedMarkBox::submit() {
if (_text->getLastText().trimmed().isEmpty()) {
_text->setFocus();
_text->showError();
} else {
save();
}
}
void EditEditedMarkBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_text->resize(
width()
- st::boxPadding.left()
- st::newGroupInfoPadding.left()
- st::boxPadding.right(),
_text->height());
const auto left = st::boxPadding.left() + st::newGroupInfoPadding.left();
_text->moveToLeft(left, st::contactPadding.top());
}
void EditEditedMarkBox::save() {
AyuSettings::set_editedMark(_text->getLastText());
AyuSettings::save();
closeBox();
}

View file

@ -1,28 +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, 2025
#pragma once
#include "base/timer.h"
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
class EditEditedMarkBox : public Ui::BoxContent
{
public:
EditEditedMarkBox(QWidget *);
protected:
void setInnerFocus() override;
void prepare() override;
void resizeEvent(QResizeEvent *e) override;
private:
void submit();
void save();
object_ptr<Ui::InputField> _text;
};

View file

@ -4,7 +4,9 @@
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "edit_deleted_mark.h"
#include "edit_mark_box.h"
#include <utility>
#include "boxes/peer_list_controllers.h"
#include "lang/lang_keys.h"
@ -17,19 +19,25 @@
#include "ayu/ayu_settings.h"
EditDeletedMarkBox::EditDeletedMarkBox(QWidget *)
: _text(
this,
st::defaultInputField,
tr::ayu_DeletedMarkText(),
AyuSettings::getInstance().deletedMark) {
EditMarkBox::EditMarkBox(QWidget *,
rpl::producer<QString> title,
const QString &currentValue,
QString defaultValue,
const Fn<void(const QString &)> &saveCallback)
: _title(title)
, _defaultValue(std::move(defaultValue))
, _saveCallback(saveCallback)
, _text(
this,
st::defaultInputField,
title,
currentValue) {
}
void EditDeletedMarkBox::prepare() {
const auto defaultDeletedMark = "🧹";
void EditMarkBox::prepare() {
auto newHeight = st::contactPadding.top() + _text->height();
setTitle(tr::ayu_DeletedMarkText());
setTitle(_title);
newHeight += st::boxPadding.bottom() + st::contactPadding.bottom();
setDimensions(st::boxWidth, newHeight);
@ -37,7 +45,7 @@ void EditDeletedMarkBox::prepare() {
addLeftButton(tr::ayu_BoxActionReset(),
[=]
{
_text->setText(defaultDeletedMark);
_text->setText(_defaultValue);
});
addButton(tr::lng_settings_save(),
@ -59,11 +67,11 @@ void EditDeletedMarkBox::prepare() {
) | rpl::start_with_next(submitted, _text->lifetime());
}
void EditDeletedMarkBox::setInnerFocus() {
void EditMarkBox::setInnerFocus() {
_text->setFocusFast();
}
void EditDeletedMarkBox::submit() {
void EditMarkBox::submit() {
if (_text->getLastText().trimmed().isEmpty()) {
_text->setFocus();
_text->showError();
@ -72,7 +80,7 @@ void EditDeletedMarkBox::submit() {
}
}
void EditDeletedMarkBox::resizeEvent(QResizeEvent *e) {
void EditMarkBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_text->resize(
@ -86,9 +94,7 @@ void EditDeletedMarkBox::resizeEvent(QResizeEvent *e) {
_text->moveToLeft(left, st::contactPadding.top());
}
void EditDeletedMarkBox::save() {
AyuSettings::set_deletedMark(_text->getLastText());
AyuSettings::save();
void EditMarkBox::save() {
_saveCallback(_text->getLastText());
closeBox();
}

View file

@ -9,10 +9,14 @@
#include "base/timer.h"
#include "boxes/abstract_box.h"
class EditDeletedMarkBox : public Ui::BoxContent
namespace Ui {
class InputField;
}
class EditMarkBox : public Ui::BoxContent
{
public:
EditDeletedMarkBox(QWidget *);
EditMarkBox(QWidget *, rpl::producer<QString> title, const QString& currentValue, QString defaultValue, const Fn<void(const QString&)> &saveCallback);
protected:
void setInnerFocus() override;
@ -23,5 +27,9 @@ private:
void submit();
void save();
rpl::producer<QString> _title;
QString _defaultValue;
Fn<void(const QString&)> _saveCallback;
object_ptr<Ui::InputField> _text;
};

View file

@ -38,7 +38,7 @@ void MessageShotBox::prepare() {
void MessageShotBox::setupContent() {
_selectedPalette = std::make_shared<style::palette>();
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
const auto savedShowColorfulReplies = !settings.simpleQuotesAndReplies;
using namespace Settings;

View file

@ -37,6 +37,7 @@ const QVector<QString> icons{
AyuAssets::CHIBI2_ICON,
AyuAssets::EXTERA2_ICON,
};
std::unordered_map<QString, QImage> cachedIcons;
const auto rows = static_cast<int>(icons.size()) / 4 + std::min(1, static_cast<int>(icons.size()) % 4);
@ -83,6 +84,9 @@ IconPicker::IconPicker(QWidget *parent)
setMinimumSize(st::boxWidth, (st::cpIconSize + st::cpPadding) * rows - st::cpPadding);
}
IconPicker::~IconPicker() {
cachedIcons.clear();
}
void IconPicker::paintEvent(QPaintEvent *e) {
Painter p(this);
PainterHighQualityEnabler hq(p);
@ -98,10 +102,16 @@ void IconPicker::paintEvent(QPaintEvent *e) {
if (iconName.isEmpty()) {
continue;
}
auto icon = AyuAssets::loadPreview(iconName)
.scaled(st::cpIconSize, st::cpIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QImage icon;
if (const auto cached = cachedIcons.find(iconName); cached != cachedIcons.end()) {
icon = cached->second;
} else {
icon = cachedIcons[iconName] = AyuAssets::loadPreview(iconName).scaled(
st::cpIconSize,
st::cpIconSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
}
auto opacity = 0.0f;
if (iconName == wasSelected) {
opacity = 1.0f - animation.value(1.0f);
@ -121,7 +131,7 @@ void IconPicker::paintEvent(QPaintEvent *e) {
}
void IconPicker::mousePressEvent(QMouseEvent *e) {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
auto changed = false;
auto x = e->pos().x();

View file

@ -13,6 +13,7 @@ class IconPicker : public Ui::RpWidget
{
public:
IconPicker(QWidget *parent);
~IconPicker();
protected:
void paintEvent(QPaintEvent *e) override;

View file

@ -20,25 +20,168 @@
#include "core/mime_type.h"
#include "styles/style_ayu_icons.h"
#include "styles/style_menu_icons.h"
#include "styles/style_layers.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/message_history/history_section.h"
#include "ayu/utils/telegram_helpers.h"
#include "base/call_delayed.h"
#include "base/random.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_search_controller.h"
#include "data/data_session.h"
#include "history/view/history_view_context_menu.h"
#include "history/view/history_view_element.h"
#include "ui/boxes/confirm_box.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
namespace AyuUi {
namespace {
void DeleteMyMessagesAfterConfirm(not_null<PeerData*> peer) {
const auto session = &peer->session();
auto collected = std::make_shared<std::vector<MsgId>>();
const auto removeNext = std::make_shared<Fn<void(int)>>();
const auto requestNext = std::make_shared<Fn<void(MsgId)>>();
*removeNext = [=](int index)
{
if (index >= int(collected->size())) {
DEBUG_LOG(("Deleted all %1 my messages in this chat").arg(collected->size()));
return;
}
QVector<MTPint> ids;
ids.reserve(std::min<int>(100, collected->size() - index));
for (auto i = 0; i < 100 && (index + i) < int(collected->size()); ++i) {
ids.push_back(MTP_int((*collected)[index + i].bare));
}
const auto batch = index / 100 + 1;
const auto done = [=](const MTPmessages_AffectedMessages &result)
{
session->api().applyAffectedMessages(peer, result);
if (peer->isChannel()) {
session->data().processMessagesDeleted(peer->id, ids);
} else {
session->data().processNonChannelMessagesDeleted(ids);
}
const auto deleted = index + ids.size();
DEBUG_LOG(("Deleted batch %1, total deleted %2/%3").arg(batch).arg(deleted).arg(collected->size()));
const auto delay = crl::time(500 + base::RandomValue<int>() % 500);
base::call_delayed(delay, [=] { (*removeNext)(deleted); });
};
const auto fail = [=](const MTP::Error &error)
{
DEBUG_LOG(("Delete batch failed: %1").arg(error.type()));
const auto delay = crl::time(1000);
base::call_delayed(delay, [=] { (*removeNext)(index); });
};
if (const auto channel = peer->asChannel()) {
session->api()
.request(MTPchannels_DeleteMessages(channel->inputChannel, MTP_vector<MTPint>(ids)))
.done(done)
.fail(fail)
.handleFloodErrors()
.send();
} else {
using Flag = MTPmessages_DeleteMessages::Flag;
session->api()
.request(MTPmessages_DeleteMessages(MTP_flags(Flag::f_revoke), MTP_vector<MTPint>(ids)))
.done(done)
.fail(fail)
.handleFloodErrors()
.send();
}
};
*requestNext = [=](MsgId from)
{
using Flag = MTPmessages_Search::Flag;
auto request = MTPmessages_Search(
MTP_flags(Flag::f_from_id),
peer->input,
MTP_string(),
MTP_inputPeerSelf(),
MTPInputPeer(),
MTPVector<MTPReaction>(),
MTP_int(0),
// top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
// min_date
MTP_int(0),
// max_date
MTP_int(from.bare),
MTP_int(0),
// add_offset
MTP_int(100),
MTP_int(0),
// max_id
MTP_int(0),
// min_id
MTP_long(0)); // hash
session->api()
.request(std::move(request))
.done([=](const Api::HistoryRequestResult &result)
{
auto parsed = Api::ParseHistoryResult(peer, from, Data::LoadDirection::Before, result);
MsgId minId;
int batchCount = 0;
for (const auto &id : parsed.messageIds) {
if (!minId || id < minId) minId = id;
collected->push_back(id);
++batchCount;
}
DEBUG_LOG(("Batch found %1 my messages, total %2").arg(batchCount).arg(collected->size()));
if (parsed.messageIds.size() == 100 && minId) {
(*requestNext)(minId - MsgId(1));
} else {
DEBUG_LOG(("Found %1 my messages in this chat (SEARCH)").arg(collected->size()));
(*removeNext)(0);
}
})
.fail([=](const MTP::Error &error) { DEBUG_LOG(("History fetch failed: %1").arg(error.type())); })
.send();
};
(*requestNext)(MsgId(0));
}
Fn<void()> DeleteMyMessagesHandler(not_null<Window::SessionController*> controller, not_null<PeerData*> peer) {
return [=]
{
if (!controller->showFrozenError()) {
controller->show(Ui::MakeConfirmBox({
.text = tr::ayu_DeleteOwnMessagesConfirmation(tr::now),
.confirmed =
[=](Fn<void()> &&close)
{
DeleteMyMessagesAfterConfirm(peer);
close();
},
.confirmText = tr::lng_box_delete(),
.cancelText = tr::lng_cancel(),
.confirmStyle = &st::attentionBoxButton,
}));
}
};
}
}
bool needToShowItem(int state) {
return state == 1 || (state == 2 && base::IsExtendedContextMenuModifierPressed());
}
@ -170,6 +313,34 @@ void AddOpenChannelAction(PeerData *peerData,
&st::menuIconChannel);
}
void AddDeleteOwnMessagesAction(PeerData *peerData,
Data::ForumTopic *topic,
not_null<Window::SessionController*> sessionController,
const Window::PeerMenuCallback &addCallback) {
if (topic) {
return;
}
const auto isGroup = peerData->isChat() || peerData->isMegagroup();
if (!isGroup) {
return;
}
if (const auto chat = peerData->asChat()) {
if (!chat->amIn() || chat->amCreator() || chat->hasAdminRights()) {
return;
}
} else if (const auto channel = peerData->asChannel()) {
if (!channel->isMegagroup() || !channel->amIn() || channel->amCreator() || channel->hasAdminRights()) {
return;
}
} else {
return;
}
addCallback(
tr::ayu_DeleteOwnMessages(tr::now),
DeleteMyMessagesHandler(sessionController, peerData),
&st::menuIconTTL);
}
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
if (item->hideEditedBadge()) {
return;
@ -197,7 +368,7 @@ void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
}
void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!needToShowItem(settings.showHideMessageInContextMenu)) {
return;
}
@ -207,20 +378,25 @@ void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
}
const auto history = item->history();
const auto owner = &history->owner();
menu->addAction(
tr::ayu_ContextHideMessage(tr::now),
[=]()
{
item->destroy();
const auto ids = owner->itemOrItsGroup(item);
for (const auto &fullId : ids) {
if (const auto current = owner->message(fullId)) {
current->destroy();
AyuState::hide(current);
}
}
history->requestChatListMessage();
AyuState::hide(item);
},
&st::menuIconClear);
}
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!needToShowItem(settings.showUserMessagesInContextMenu)) {
return;
}
@ -245,7 +421,7 @@ void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
}
void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (!needToShowItem(settings.showMessageDetailsInContextMenu)) {
return;
}
@ -464,7 +640,7 @@ void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
return;
}
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (settings.sendReadMessages) {
return;
}

View file

@ -28,6 +28,10 @@ void AddJumpToBeginningAction(PeerData *peerData,
void AddOpenChannelAction(PeerData *peerData,
not_null<Window::SessionController*> sessionController,
const Window::PeerMenuCallback &addCallback);
void AddDeleteOwnMessagesAction(PeerData *peerData,
Data::ForumTopic* topic,
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);

View file

@ -594,8 +594,9 @@ void InnerWidget::elementSearchInList(
void InnerWidget::elementHandleViaClick(not_null<UserData*> bot) {
}
bool InnerWidget::elementIsChatWide() {
return _isChatWide;
HistoryView::ElementChatMode InnerWidget::elementChatMode() {
using Mode = HistoryView::ElementChatMode;
return _isChatWide ? Mode::Wide : Mode::Default;
}
not_null<Ui::PathShiftGradient*> InnerWidget::elementPathShiftGradient() {

View file

@ -120,7 +120,7 @@ public:
const QString &query,
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
bool elementIsChatWide() override;
HistoryView::ElementChatMode elementChatMode() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullReplyTo &to) override;
void elementStartInteraction(

View file

@ -9,6 +9,7 @@
#include "api/api_chat_participants.h"
#include "api/api_text_entities.h"
#include "ayu/utils/ayu_mapper.h"
#include "ayu/ui/message_history/history_inner.h"
#include "base/unixtime.h"
#include "core/application.h"
@ -118,7 +119,10 @@ void GenerateItems(
};
const auto text = QString::fromStdString(message.text);
addSimpleTextMessage(Ui::Text::WithEntities(text));
auto textAndEntities = Ui::Text::WithEntities(text);
const auto entities = AyuMapper::deserializeTextWithEntities(message.textEntities);
textAndEntities.entities = Api::EntitiesFromMTP(&history->session(), entities.v);
addSimpleTextMessage(std::move(textAndEntities));
}
} // namespace MessageHistory

View file

@ -7,8 +7,7 @@
#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"
#include "ayu/ui/boxes/edit_mark_box.h"
#include "ayu/ui/boxes/font_selector.h"
#include "lang_auto.h"
@ -27,7 +26,7 @@
#include "styles/style_settings.h"
#include "styles/style_widgets.h"
#include "icon_picker.h"
#include "../components/icon_picker.h"
#include "tray.h"
#include "core/application.h"
#include "main/main_domain.h"
@ -37,7 +36,6 @@
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/single_choice_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/continuous_sliders.h"
@ -464,41 +462,41 @@ Ayu::Ayu(
}
void SetupGhostModeToggle(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_GhostEssentialsHeader());
std::vector checkboxes{
NestedEntry{
tr::ayu_DontReadMessages(tr::now), !settings.sendReadMessages, [=](bool enabled)
tr::ayu_DontReadMessages(tr::now), !settings->sendReadMessages, [=](bool enabled)
{
AyuSettings::set_sendReadMessages(!enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_DontReadStories(tr::now), !settings.sendReadStories, [=](bool enabled)
tr::ayu_DontReadStories(tr::now), !settings->sendReadStories, [=](bool enabled)
{
AyuSettings::set_sendReadStories(!enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_DontSendOnlinePackets(tr::now), !settings.sendOnlinePackets, [=](bool enabled)
tr::ayu_DontSendOnlinePackets(tr::now), !settings->sendOnlinePackets, [=](bool enabled)
{
AyuSettings::set_sendOnlinePackets(!enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_DontSendUploadProgress(tr::now), !settings.sendUploadProgress, [=](bool enabled)
tr::ayu_DontSendUploadProgress(tr::now), !settings->sendUploadProgress, [=](bool enabled)
{
AyuSettings::set_sendUploadProgress(!enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_SendOfflinePacketAfterOnline(tr::now), settings.sendOfflinePacketAfterOnline, [=](bool enabled)
tr::ayu_SendOfflinePacketAfterOnline(tr::now), settings->sendOfflinePacketAfterOnline, [=](bool enabled)
{
AyuSettings::set_sendOfflinePacketAfterOnline(enabled);
AyuSettings::save();
@ -510,13 +508,14 @@ void SetupGhostModeToggle(not_null<Ui::VerticalLayout*> container) {
}
void SetupGhostEssentials(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
SetupGhostModeToggle(container);
auto markReadAfterActionVal = container->lifetime().make_state<rpl::variable<bool>>(settings.sendOfflinePacketAfterOnline);
auto markReadAfterActionVal = container->lifetime().make_state<rpl::variable<bool>>(
settings->markReadAfterAction);
auto useScheduledMessagesVal = container->lifetime().make_state<rpl::variable<
bool>>(settings.useScheduledMessages);
bool>>(settings->useScheduledMessages);
AddButtonWithIcon(
container,
@ -528,7 +527,7 @@ void SetupGhostEssentials(not_null<Ui::VerticalLayout*> container) {
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.sendOfflinePacketAfterOnline);
return (enabled != settings->markReadAfterAction);
}) | start_with_next(
[=](bool enabled)
{
@ -555,7 +554,7 @@ void SetupGhostEssentials(not_null<Ui::VerticalLayout*> container) {
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.useScheduledMessages);
return (enabled != settings->useScheduledMessages);
}) | start_with_next(
[=](bool enabled)
{
@ -577,12 +576,12 @@ void SetupGhostEssentials(not_null<Ui::VerticalLayout*> container) {
tr::ayu_SendWithoutSoundByDefault(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.sendWithoutSound)
rpl::single(settings->sendWithoutSound)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.sendWithoutSound);
return (enabled != settings->sendWithoutSound);
}) | start_with_next(
[=](bool enabled)
{
@ -595,7 +594,7 @@ void SetupGhostEssentials(not_null<Ui::VerticalLayout*> container) {
}
void SetupSpyEssentials(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_SpyEssentialsHeader());
@ -604,12 +603,12 @@ void SetupSpyEssentials(not_null<Ui::VerticalLayout*> container) {
tr::ayu_SaveDeletedMessages(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.saveDeletedMessages)
rpl::single(settings->saveDeletedMessages)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.saveDeletedMessages);
return (enabled != settings->saveDeletedMessages);
}) | start_with_next(
[=](bool enabled)
{
@ -623,12 +622,12 @@ void SetupSpyEssentials(not_null<Ui::VerticalLayout*> container) {
tr::ayu_SaveMessagesHistory(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.saveMessagesHistory)
rpl::single(settings->saveMessagesHistory)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.saveMessagesHistory);
return (enabled != settings->saveMessagesHistory);
}) | start_with_next(
[=](bool enabled)
{
@ -646,12 +645,12 @@ void SetupSpyEssentials(not_null<Ui::VerticalLayout*> container) {
tr::ayu_MessageSavingSaveForBots(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.saveForBots)
rpl::single(settings->saveForBots)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.saveForBots);
return (enabled != settings->saveForBots);
}) | start_with_next(
[=](bool enabled)
{
@ -662,7 +661,7 @@ void SetupSpyEssentials(not_null<Ui::VerticalLayout*> container) {
}
void SetupMessageFilters(not_null<Ui::VerticalLayout*> container) {
auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_RegexFilters());
@ -671,12 +670,12 @@ void SetupMessageFilters(not_null<Ui::VerticalLayout*> container) {
tr::ayu_FiltersHideFromBlocked(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.hideFromBlocked)
rpl::single(settings->hideFromBlocked)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.hideFromBlocked);
return (enabled != settings->hideFromBlocked);
}) | start_with_next(
[=](bool enabled)
{
@ -687,7 +686,7 @@ void SetupMessageFilters(not_null<Ui::VerticalLayout*> container) {
}
void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_QoLTogglesHeader());
@ -696,12 +695,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
tr::ayu_DisableAds(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.disableAds)
rpl::single(settings->disableAds)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.disableAds);
return (enabled != settings->disableAds);
}) | start_with_next(
[=](bool enabled)
{
@ -715,12 +714,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
tr::ayu_DisableStories(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.disableStories)
rpl::single(settings->disableStories)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.disableStories);
return (enabled != settings->disableStories);
}) | start_with_next(
[=](bool enabled)
{
@ -734,12 +733,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
tr::ayu_DisableCustomBackgrounds(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.disableCustomBackgrounds)
rpl::single(settings->disableCustomBackgrounds)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.disableCustomBackgrounds);
return (enabled != settings->disableCustomBackgrounds);
}) | start_with_next(
[=](bool enabled)
{
@ -753,12 +752,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
tr::ayu_SimpleQuotesAndReplies(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.simpleQuotesAndReplies)
rpl::single(settings->simpleQuotesAndReplies)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.simpleQuotesAndReplies);
return (enabled != settings->simpleQuotesAndReplies);
}) | start_with_next(
[=](bool enabled)
{
@ -769,14 +768,14 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
std::vector checkboxes = {
NestedEntry{
tr::ayu_CollapseSimilarChannels(tr::now), settings.collapseSimilarChannels, [=](bool enabled)
tr::ayu_CollapseSimilarChannels(tr::now), settings->collapseSimilarChannels, [=](bool enabled)
{
AyuSettings::set_collapseSimilarChannels(enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_HideSimilarChannelsTab(tr::now), settings.hideSimilarChannels, [=](bool enabled)
tr::ayu_HideSimilarChannelsTab(tr::now), settings->hideSimilarChannels, [=](bool enabled)
{
AyuSettings::set_hideSimilarChannels(enabled);
AyuSettings::save();
@ -795,12 +794,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
tr::ayu_DisableNotificationsDelay(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.disableNotificationsDelay)
rpl::single(settings->disableNotificationsDelay)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.disableNotificationsDelay);
return (enabled != settings->disableNotificationsDelay);
}) | start_with_next(
[=](bool enabled)
{
@ -814,12 +813,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
tr::ayu_ShowOnlyAddedEmojisAndStickers(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.showOnlyAddedEmojisAndStickers)
rpl::single(settings->showOnlyAddedEmojisAndStickers)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showOnlyAddedEmojisAndStickers);
return (enabled != settings->showOnlyAddedEmojisAndStickers);
}) | start_with_next(
[=](bool enabled)
{
@ -833,12 +832,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
tr::ayu_LocalPremium(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.localPremium)
rpl::single(settings->localPremium)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.localPremium);
return (enabled != settings->localPremium);
}) | start_with_next(
[=](bool enabled)
{
@ -846,6 +845,48 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDivider(container);
AddSkip(container);
AddButtonWithIcon(
container,
tr::ayu_HideChannelReactions(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(!settings->showChannelReactions)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (!enabled != settings->showChannelReactions);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_hideChannelReactions(!enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_HideGroupReactions(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(!settings->showGroupReactions)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (!enabled != settings->showGroupReactions);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_hideGroupReactions(!enabled);
AyuSettings::save();
},
container->lifetime());
}
void SetupAppIcon(not_null<Ui::VerticalLayout*> container) {
@ -856,7 +897,7 @@ void SetupAppIcon(not_null<Ui::VerticalLayout*> container) {
void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSkip(container);
AddSubsectionTitle(container, tr::ayu_ContextMenuElementsHeader());
@ -870,7 +911,7 @@ void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
AddChooseButtonWithIconAndRightText(
container,
controller,
settings.showReactionsPanelInContextMenu,
settings->showReactionsPanelInContextMenu,
options,
tr::ayu_SettingsContextMenuReactionsPanel(),
tr::ayu_SettingsContextMenuTitle(),
@ -883,7 +924,7 @@ void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
AddChooseButtonWithIconAndRightText(
container,
controller,
settings.showViewsPanelInContextMenu,
settings->showViewsPanelInContextMenu,
options,
tr::ayu_SettingsContextMenuViewsPanel(),
tr::ayu_SettingsContextMenuTitle(),
@ -897,7 +938,7 @@ void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
AddChooseButtonWithIconAndRightText(
container,
controller,
settings.showHideMessageInContextMenu,
settings->showHideMessageInContextMenu,
options,
tr::ayu_ContextHideMessage(),
tr::ayu_SettingsContextMenuTitle(),
@ -910,7 +951,7 @@ void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
AddChooseButtonWithIconAndRightText(
container,
controller,
settings.showUserMessagesInContextMenu,
settings->showUserMessagesInContextMenu,
options,
tr::ayu_UserMessagesMenuText(),
tr::ayu_SettingsContextMenuTitle(),
@ -923,7 +964,7 @@ void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
AddChooseButtonWithIconAndRightText(
container,
controller,
settings.showMessageDetailsInContextMenu,
settings->showMessageDetailsInContextMenu,
options,
tr::ayu_MessageDetailsPC(),
tr::ayu_SettingsContextMenuTitle(),
@ -939,7 +980,7 @@ void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
}
void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSkip(container);
AddSubsectionTitle(container, tr::ayu_MessageFieldElementsHeader());
@ -950,12 +991,12 @@ void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::messageFieldAttachIcon}
)->toggleOn(
rpl::single(settings.showAttachButtonInMessageField)
rpl::single(settings->showAttachButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showAttachButtonInMessageField);
return (enabled != settings->showAttachButtonInMessageField);
}) | start_with_next(
[=](bool enabled)
{
@ -970,12 +1011,12 @@ void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::messageFieldCommandsIcon}
)->toggleOn(
rpl::single(settings.showCommandsButtonInMessageField)
rpl::single(settings->showCommandsButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showCommandsButtonInMessageField);
return (enabled != settings->showCommandsButtonInMessageField);
}) | start_with_next(
[=](bool enabled)
{
@ -990,12 +1031,12 @@ void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::messageFieldTTLIcon}
)->toggleOn(
rpl::single(settings.showAutoDeleteButtonInMessageField)
rpl::single(settings->showAutoDeleteButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showAutoDeleteButtonInMessageField);
return (enabled != settings->showAutoDeleteButtonInMessageField);
}) | start_with_next(
[=](bool enabled)
{
@ -1010,12 +1051,12 @@ void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::messageFieldEmojiIcon}
)->toggleOn(
rpl::single(settings.showEmojiButtonInMessageField)
rpl::single(settings->showEmojiButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showEmojiButtonInMessageField);
return (enabled != settings->showEmojiButtonInMessageField);
}) | start_with_next(
[=](bool enabled)
{
@ -1030,12 +1071,12 @@ void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::messageFieldVoiceIcon}
)->toggleOn(
rpl::single(settings.showMicrophoneButtonInMessageField)
rpl::single(settings->showMicrophoneButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showMicrophoneButtonInMessageField);
return (enabled != settings->showMicrophoneButtonInMessageField);
}) | start_with_next(
[=](bool enabled)
{
@ -1049,7 +1090,7 @@ void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
}
void SetupMessageFieldPopups(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSkip(container);
AddSubsectionTitle(container, tr::ayu_MessageFieldPopupsHeader());
@ -1060,12 +1101,12 @@ void SetupMessageFieldPopups(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::messageFieldAttachIcon}
)->toggleOn(
rpl::single(settings.showAttachPopup)
rpl::single(settings->showAttachPopup)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showAttachPopup);
return (enabled != settings->showAttachPopup);
}) | start_with_next(
[=](bool enabled)
{
@ -1080,12 +1121,12 @@ void SetupMessageFieldPopups(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::messageFieldEmojiIcon}
)->toggleOn(
rpl::single(settings.showEmojiPopup)
rpl::single(settings->showEmojiPopup)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showEmojiPopup);
return (enabled != settings->showEmojiPopup);
}) | start_with_next(
[=](bool enabled)
{
@ -1099,7 +1140,7 @@ void SetupMessageFieldPopups(not_null<Ui::VerticalLayout*> container) {
}
void SetupDrawerElements(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSkip(container);
AddSubsectionTitle(container, tr::ayu_DrawerElementsHeader());
@ -1110,12 +1151,12 @@ void SetupDrawerElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::ayuLReadMenuIcon}
)->toggleOn(
rpl::single(settings.showLReadToggleInDrawer)
rpl::single(settings->showLReadToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showLReadToggleInDrawer);
return (enabled != settings->showLReadToggleInDrawer);
}) | start_with_next(
[=](bool enabled)
{
@ -1130,12 +1171,12 @@ void SetupDrawerElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::ayuSReadMenuIcon}
)->toggleOn(
rpl::single(settings.showSReadToggleInDrawer)
rpl::single(settings->showSReadToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showSReadToggleInDrawer);
return (enabled != settings->showSReadToggleInDrawer);
}) | start_with_next(
[=](bool enabled)
{
@ -1150,12 +1191,12 @@ void SetupDrawerElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::ayuGhostIcon}
)->toggleOn(
rpl::single(settings.showGhostToggleInDrawer)
rpl::single(settings->showGhostToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showGhostToggleInDrawer);
return (enabled != settings->showGhostToggleInDrawer);
}) | start_with_next(
[=](bool enabled)
{
@ -1171,12 +1212,12 @@ void SetupDrawerElements(not_null<Ui::VerticalLayout*> container) {
st::settingsButton,
{&st::ayuStreamerModeMenuIcon}
)->toggleOn(
rpl::single(settings.showStreamerToggleInDrawer)
rpl::single(settings->showStreamerToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showStreamerToggleInDrawer);
return (enabled != settings->showStreamerToggleInDrawer);
}) | start_with_next(
[=](bool enabled)
{
@ -1188,7 +1229,7 @@ void SetupDrawerElements(not_null<Ui::VerticalLayout*> container) {
}
void SetupTrayElements(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSkip(container);
AddSubsectionTitle(container, tr::ayu_TrayElementsHeader());
@ -1198,12 +1239,12 @@ void SetupTrayElements(not_null<Ui::VerticalLayout*> container) {
tr::ayu_EnableGhostModeTray(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.showGhostToggleInTray)
rpl::single(settings->showGhostToggleInTray)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showGhostToggleInTray);
return (enabled != settings->showGhostToggleInTray);
}) | start_with_next(
[=](bool enabled)
{
@ -1218,12 +1259,12 @@ void SetupTrayElements(not_null<Ui::VerticalLayout*> container) {
tr::ayu_EnableStreamerModeTray(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.showStreamerToggleInTray)
rpl::single(settings->showStreamerToggleInTray)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showStreamerToggleInTray);
return (enabled != settings->showStreamerToggleInTray);
}) | start_with_next(
[=](bool enabled)
{
@ -1236,7 +1277,7 @@ void SetupTrayElements(not_null<Ui::VerticalLayout*> container) {
void SetupShowPeerId(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
const auto options = std::vector{
QString(tr::ayu_SettingsShowID_Hide(tr::now)),
@ -1270,7 +1311,7 @@ void SetupShowPeerId(not_null<Ui::VerticalLayout*> container,
{
.title = tr::ayu_SettingsShowID(),
.options = options,
.initialSelection = settings.showPeerId,
.initialSelection = settings->showPeerId,
.callback = save,
});
}));
@ -1278,7 +1319,7 @@ void SetupShowPeerId(not_null<Ui::VerticalLayout*> container,
}
void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
container->add(
object_ptr<Button>(container,
@ -1300,7 +1341,7 @@ void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout*> container) {
{
label->setText(QString::number(amount));
};
updateLabel(settings.recentStickersCount);
updateLabel(settings->recentStickersCount);
slider->setPseudoDiscrete(
200 + 1,
@ -1309,7 +1350,7 @@ void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout*> container) {
{
return amount;
},
settings.recentStickersCount,
settings->recentStickersCount,
[=](int amount)
{
updateLabel(amount);
@ -1325,7 +1366,7 @@ void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout*> container) {
void SetupWideMultiplierSlider(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
container->add(
object_ptr<Button>(container,
@ -1360,12 +1401,12 @@ void SetupWideMultiplierSlider(not_null<Ui::VerticalLayout*> container,
return kMinSize + index * kStep;
};
updateLabel(settings.wideMultiplier);
updateLabel(settings->wideMultiplier);
slider->setPseudoDiscrete(
kSizeAmount,
[=](int index) { return index; },
valueToIndex(settings.wideMultiplier),
valueToIndex(settings->wideMultiplier),
[=](int index)
{
updateLabel(indexToValue(index));
@ -1394,13 +1435,13 @@ void SetupWideMultiplierSlider(not_null<Ui::VerticalLayout*> container,
}
void SetupFonts(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
const auto monoButton = AddButtonWithLabel(
container,
tr::ayu_MonospaceFont(),
rpl::single(
settings.monoFont.isEmpty() ? tr::ayu_FontDefault(tr::now) : settings.monoFont
settings->monoFont.isEmpty() ? tr::ayu_FontDefault(tr::now) : settings->monoFont
),
st::settingsButtonNoIcon);
const auto monoGuard = Ui::CreateChild<base::binary_guard>(monoButton.get());
@ -1419,7 +1460,7 @@ void SetupFonts(not_null<Ui::VerticalLayout*> container, not_null<Window::Sessio
}
void SetupSendConfirmations(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_ConfirmationsTitle());
@ -1428,12 +1469,12 @@ void SetupSendConfirmations(not_null<Ui::VerticalLayout*> container) {
tr::ayu_StickerConfirmation(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.stickerConfirmation)
rpl::single(settings->stickerConfirmation)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.stickerConfirmation);
return (enabled != settings->stickerConfirmation);
}) | start_with_next(
[=](bool enabled)
{
@ -1447,12 +1488,12 @@ void SetupSendConfirmations(not_null<Ui::VerticalLayout*> container) {
tr::ayu_GIFConfirmation(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.gifConfirmation)
rpl::single(settings->gifConfirmation)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.gifConfirmation);
return (enabled != settings->gifConfirmation);
}) | start_with_next(
[=](bool enabled)
{
@ -1466,12 +1507,12 @@ void SetupSendConfirmations(not_null<Ui::VerticalLayout*> container) {
tr::ayu_VoiceConfirmation(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.voiceConfirmation)
rpl::single(settings->voiceConfirmation)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.voiceConfirmation);
return (enabled != settings->voiceConfirmation);
}) | start_with_next(
[=](bool enabled)
{
@ -1482,19 +1523,19 @@ void SetupSendConfirmations(not_null<Ui::VerticalLayout*> container) {
}
void SetupMarks(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddButtonWithIcon(
container,
tr::ayu_ReplaceMarksWithIcons(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.replaceBottomInfoWithIcons)
rpl::single(settings->replaceBottomInfoWithIcons)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.replaceBottomInfoWithIcons);
return (enabled != settings->replaceBottomInfoWithIcons);
}) | start_with_next(
[=](bool enabled)
{
@ -1511,7 +1552,16 @@ void SetupMarks(not_null<Ui::VerticalLayout*> container) {
)->addClickHandler(
[=]()
{
auto box = Box<EditDeletedMarkBox>();
auto box = Box<EditMarkBox>(
tr::ayu_DeletedMarkText(),
settings->deletedMark,
QString("🧹"),
[=](const QString &value)
{
AyuSettings::set_deletedMark(value);
AyuSettings::save();
}
);
Ui::show(std::move(box));
});
@ -1523,25 +1573,34 @@ void SetupMarks(not_null<Ui::VerticalLayout*> container) {
)->addClickHandler(
[=]()
{
auto box = Box<EditEditedMarkBox>();
auto box = Box<EditMarkBox>(
tr::ayu_EditedMarkText(),
settings->editedMark,
tr::lng_edited(tr::now),
[=](const QString &value)
{
AyuSettings::set_editedMark(value);
AyuSettings::save();
}
);
Ui::show(std::move(box));
});
}
void SetupFolderSettings(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddButtonWithIcon(
container,
tr::ayu_HideNotificationCounters(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.hideNotificationCounters)
rpl::single(settings->hideNotificationCounters)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.hideNotificationCounters);
return (enabled != settings->hideNotificationCounters);
}) | start_with_next(
[=](bool enabled)
{
@ -1557,12 +1616,12 @@ void SetupFolderSettings(not_null<Ui::VerticalLayout*> container, not_null<Windo
tr::ayu_HideNotificationBadge(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.hideNotificationBadge)
rpl::single(settings->hideNotificationBadge)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.hideNotificationBadge);
return (enabled != settings->hideNotificationBadge);
}) | start_with_next(
[=](bool enabled)
{
@ -1581,12 +1640,12 @@ void SetupFolderSettings(not_null<Ui::VerticalLayout*> container, not_null<Windo
tr::ayu_HideAllChats(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.hideAllChatsFolder)
rpl::single(settings->hideAllChatsFolder)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.hideAllChatsFolder);
return (enabled != settings->hideAllChatsFolder);
}) | start_with_next(
[=](bool enabled)
{
@ -1597,7 +1656,7 @@ void SetupFolderSettings(not_null<Ui::VerticalLayout*> container, not_null<Windo
}
void SetupChannelSettings(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
const auto options = std::vector{
tr::ayu_ChannelBottomButtonHide(tr::now),
@ -1608,7 +1667,7 @@ void SetupChannelSettings(not_null<Ui::VerticalLayout*> container, not_null<Wind
AddChooseButtonWithIconAndRightText(
container,
controller,
settings.channelBottomButton,
settings->channelBottomButton,
options,
tr::ayu_ChannelBottomButton(),
tr::ayu_ChannelBottomButton(),
@ -1620,7 +1679,7 @@ void SetupChannelSettings(not_null<Ui::VerticalLayout*> container, not_null<Wind
}
void SetupNerdSettings(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
SetupShowPeerId(container, controller);
@ -1629,12 +1688,12 @@ void SetupNerdSettings(not_null<Ui::VerticalLayout*> container, not_null<Window:
tr::ayu_SettingsShowMessageSeconds(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.showMessageSeconds)
rpl::single(settings->showMessageSeconds)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showMessageSeconds);
return (enabled != settings->showMessageSeconds);
}) | start_with_next(
[=](bool enabled)
{
@ -1648,12 +1707,12 @@ void SetupNerdSettings(not_null<Ui::VerticalLayout*> container, not_null<Window:
tr::ayu_SettingsShowMessageShot(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.showMessageShot)
rpl::single(settings->showMessageShot)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.showMessageShot);
return (enabled != settings->showMessageShot);
}) | start_with_next(
[=](bool enabled)
{
@ -1664,7 +1723,7 @@ void SetupNerdSettings(not_null<Ui::VerticalLayout*> container, not_null<Window:
}
void SetupWebviewSettings(not_null<Ui::VerticalLayout*> container) {
const auto& settings = AyuSettings::getInstance();
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, rpl::single(QString("Webview")));
@ -1673,12 +1732,12 @@ void SetupWebviewSettings(not_null<Ui::VerticalLayout*> container) {
tr::ayu_SettingsSpoofWebviewAsAndroid(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings.spoofWebviewAsAndroid)
rpl::single(settings->spoofWebviewAsAndroid)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings.spoofWebviewAsAndroid);
return (enabled != settings->spoofWebviewAsAndroid);
}) | start_with_next(
[=](bool enabled)
{
@ -1689,14 +1748,14 @@ void SetupWebviewSettings(not_null<Ui::VerticalLayout*> container) {
std::vector checkboxes = {
NestedEntry{
tr::ayu_SettingsIncreaseWebviewHeight(tr::now), settings.increaseWebviewHeight, [=](bool enabled)
tr::ayu_SettingsIncreaseWebviewHeight(tr::now), settings->increaseWebviewHeight, [=](bool enabled)
{
AyuSettings::set_increaseWebviewHeight(enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_SettingsIncreaseWebviewWidth(tr::now), settings.increaseWebviewWidth, [=](bool enabled)
tr::ayu_SettingsIncreaseWebviewWidth(tr::now), settings->increaseWebviewWidth, [=](bool enabled)
{
AyuSettings::set_increaseWebviewWidth(enabled);
AyuSettings::save();

View file

@ -15,7 +15,7 @@ constexpr auto kMaxChannelId = -1000000000000;
QString IDString(not_null<PeerData*> peer) {
auto resultId = QString::number(getBareID(peer));
const auto& settings = AyuSettings::getInstance();
const auto &settings = AyuSettings::getInstance();
if (settings.showPeerId == 2) {
if (peer->isChannel()) {
resultId = QString::number(peerToChannel(peer->id).bare - kMaxChannelId).prepend("-");

Some files were not shown because too many files have changed in this diff Show more