2024-01-04 16:02:36 +01:00
|
|
|
//
|
2024-05-29 11:17:15 +02:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
2024-01-04 16:02:36 +01:00
|
|
|
//
|
|
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
//
|
|
|
|
#pragma once
|
|
|
|
|
2024-01-08 15:27:32 +01:00
|
|
|
#include "td/telegram/ChannelId.h"
|
|
|
|
#include "td/telegram/ChatId.h"
|
2024-01-08 13:23:16 +01:00
|
|
|
#include "td/telegram/DialogAdministrator.h"
|
2024-01-04 16:30:12 +01:00
|
|
|
#include "td/telegram/DialogId.h"
|
2024-01-08 15:27:32 +01:00
|
|
|
#include "td/telegram/DialogInviteLink.h"
|
2024-01-08 13:23:16 +01:00
|
|
|
#include "td/telegram/DialogParticipant.h"
|
2024-03-02 00:02:01 +01:00
|
|
|
#include "td/telegram/DialogParticipantFilter.h"
|
2024-01-04 16:30:12 +01:00
|
|
|
#include "td/telegram/td_api.h"
|
2024-01-08 17:26:34 +01:00
|
|
|
#include "td/telegram/telegram_api.h"
|
2024-01-08 13:42:21 +01:00
|
|
|
#include "td/telegram/UserId.h"
|
2024-01-04 16:30:12 +01:00
|
|
|
|
2024-01-04 16:02:36 +01:00
|
|
|
#include "td/actor/actor.h"
|
2024-01-04 16:30:12 +01:00
|
|
|
#include "td/actor/MultiTimeout.h"
|
2024-01-04 16:02:36 +01:00
|
|
|
|
|
|
|
#include "td/utils/common.h"
|
2024-01-04 16:30:12 +01:00
|
|
|
#include "td/utils/FlatHashMap.h"
|
2024-01-08 12:36:18 +01:00
|
|
|
#include "td/utils/Promise.h"
|
|
|
|
#include "td/utils/Status.h"
|
2024-01-04 16:02:36 +01:00
|
|
|
|
2024-03-02 23:43:42 +01:00
|
|
|
#include <utility>
|
|
|
|
|
2024-01-04 16:02:36 +01:00
|
|
|
namespace td {
|
|
|
|
|
2024-03-02 23:22:49 +01:00
|
|
|
class ChannelParticipantFilter;
|
|
|
|
|
2024-01-04 16:02:36 +01:00
|
|
|
class Td;
|
|
|
|
|
2024-01-08 12:22:44 +01:00
|
|
|
class DialogParticipantManager final : public Actor {
|
2024-01-04 16:02:36 +01:00
|
|
|
public:
|
2024-01-08 12:22:44 +01:00
|
|
|
DialogParticipantManager(Td *td, ActorShared<> parent);
|
2024-01-08 13:23:16 +01:00
|
|
|
DialogParticipantManager(const DialogParticipantManager &) = delete;
|
|
|
|
DialogParticipantManager &operator=(const DialogParticipantManager &) = delete;
|
|
|
|
DialogParticipantManager(DialogParticipantManager &&) = delete;
|
|
|
|
DialogParticipantManager &operator=(DialogParticipantManager &&) = delete;
|
|
|
|
~DialogParticipantManager() final;
|
2024-01-04 16:02:36 +01:00
|
|
|
|
2024-02-19 00:15:05 +01:00
|
|
|
void update_user_online_member_count(UserId user_id);
|
|
|
|
|
|
|
|
void update_dialog_online_member_count(const vector<DialogParticipant> &participants, DialogId dialog_id,
|
|
|
|
bool is_from_server);
|
2024-01-04 16:30:12 +01:00
|
|
|
|
|
|
|
void on_update_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server);
|
|
|
|
|
2024-03-04 01:16:20 +01:00
|
|
|
void add_cached_channel_participants(ChannelId channel_id, const vector<UserId> &added_user_ids,
|
|
|
|
UserId inviter_user_id, int32 date);
|
|
|
|
|
|
|
|
void delete_cached_channel_participant(ChannelId channel_id, UserId deleted_user_id);
|
|
|
|
|
|
|
|
void update_cached_channel_participant_status(ChannelId channel_id, UserId user_id,
|
|
|
|
const DialogParticipantStatus &status);
|
|
|
|
|
2024-01-04 16:30:12 +01:00
|
|
|
void on_dialog_opened(DialogId dialog_id);
|
|
|
|
|
|
|
|
void on_dialog_closed(DialogId dialog_id);
|
|
|
|
|
2024-01-08 12:36:18 +01:00
|
|
|
void get_dialog_join_requests(DialogId dialog_id, const string &invite_link, const string &query,
|
|
|
|
td_api::object_ptr<td_api::chatJoinRequest> offset_request, int32 limit,
|
|
|
|
Promise<td_api::object_ptr<td_api::chatJoinRequests>> &&promise);
|
|
|
|
|
|
|
|
void process_dialog_join_request(DialogId dialog_id, UserId user_id, bool approve, Promise<Unit> &&promise);
|
|
|
|
|
|
|
|
void process_dialog_join_requests(DialogId dialog_id, const string &invite_link, bool approve,
|
|
|
|
Promise<Unit> &&promise);
|
|
|
|
|
2024-01-08 13:23:16 +01:00
|
|
|
void speculative_update_dialog_administrators(DialogId dialog_id, UserId user_id,
|
|
|
|
const DialogParticipantStatus &new_status,
|
|
|
|
const DialogParticipantStatus &old_status);
|
|
|
|
|
|
|
|
void on_update_dialog_administrators(DialogId dialog_id, vector<DialogAdministrator> &&administrators,
|
|
|
|
bool have_access, bool from_database);
|
|
|
|
|
|
|
|
void get_dialog_administrators(DialogId dialog_id, Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
|
|
|
|
|
|
|
|
void reload_dialog_administrators(DialogId dialog_id, const vector<DialogAdministrator> &dialog_administrators,
|
|
|
|
Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
|
|
|
|
|
2024-01-08 15:27:32 +01:00
|
|
|
void on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped, bool force = false);
|
|
|
|
|
|
|
|
void on_update_chat_participant(ChatId chat_id, UserId user_id, int32 date, DialogInviteLink invite_link,
|
2024-04-29 14:51:42 +02:00
|
|
|
bool via_join_request,
|
2024-01-08 15:27:32 +01:00
|
|
|
telegram_api::object_ptr<telegram_api::ChatParticipant> old_participant,
|
|
|
|
telegram_api::object_ptr<telegram_api::ChatParticipant> new_participant);
|
|
|
|
|
|
|
|
void on_update_channel_participant(ChannelId channel_id, UserId user_id, int32 date, DialogInviteLink invite_link,
|
2024-04-29 14:51:42 +02:00
|
|
|
bool via_join_request, bool via_dialog_filter_invite_link,
|
2024-01-08 15:27:32 +01:00
|
|
|
telegram_api::object_ptr<telegram_api::ChannelParticipant> old_participant,
|
|
|
|
telegram_api::object_ptr<telegram_api::ChannelParticipant> new_participant);
|
|
|
|
|
|
|
|
void on_update_chat_invite_requester(DialogId dialog_id, UserId user_id, string about, int32 date,
|
|
|
|
DialogInviteLink invite_link);
|
|
|
|
|
2024-01-08 15:35:24 +01:00
|
|
|
void get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id,
|
|
|
|
Promise<td_api::object_ptr<td_api::chatMember>> &&promise);
|
|
|
|
|
2024-01-08 15:45:04 +01:00
|
|
|
void get_channel_participant(ChannelId channel_id, DialogId participant_dialog_id,
|
|
|
|
Promise<DialogParticipant> &&promise);
|
|
|
|
|
2024-03-02 23:22:49 +01:00
|
|
|
void get_channel_participants(ChannelId channel_id, td_api::object_ptr<td_api::SupergroupMembersFilter> &&filter,
|
|
|
|
string additional_query, int32 offset, int32 limit, int32 additional_limit,
|
|
|
|
Promise<DialogParticipants> &&promise);
|
|
|
|
|
2024-03-02 00:02:01 +01:00
|
|
|
void search_dialog_participants(DialogId dialog_id, const string &query, int32 limit, DialogParticipantFilter filter,
|
|
|
|
Promise<DialogParticipants> &&promise);
|
|
|
|
|
2024-04-09 16:01:51 +02:00
|
|
|
static Promise<td_api::object_ptr<td_api::failedToAddMembers>> wrap_failed_to_add_members_promise(
|
|
|
|
Promise<Unit> &&promise);
|
2024-01-08 16:15:21 +01:00
|
|
|
|
2024-04-09 16:01:51 +02:00
|
|
|
void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit,
|
|
|
|
Promise<td_api::object_ptr<td_api::failedToAddMembers>> &&promise);
|
|
|
|
|
|
|
|
void add_dialog_participants(DialogId dialog_id, const vector<UserId> &user_ids,
|
|
|
|
Promise<td_api::object_ptr<td_api::failedToAddMembers>> &&promise);
|
2024-01-08 16:15:21 +01:00
|
|
|
|
|
|
|
void set_dialog_participant_status(DialogId dialog_id, DialogId participant_dialog_id,
|
|
|
|
td_api::object_ptr<td_api::ChatMemberStatus> &&chat_member_status,
|
|
|
|
Promise<Unit> &&promise);
|
|
|
|
|
|
|
|
void ban_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id, int32 banned_until_date,
|
|
|
|
bool revoke_messages, Promise<Unit> &&promise);
|
|
|
|
|
2024-01-08 16:44:13 +01:00
|
|
|
void leave_dialog(DialogId dialog_id, Promise<Unit> &&promise);
|
|
|
|
|
2024-01-08 16:03:04 +01:00
|
|
|
void on_set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id,
|
|
|
|
DialogParticipantStatus status);
|
|
|
|
|
|
|
|
bool have_channel_participant_cache(ChannelId channel_id) const;
|
|
|
|
|
|
|
|
void add_channel_participant_to_cache(ChannelId channel_id, const DialogParticipant &dialog_participant,
|
|
|
|
bool allow_replace);
|
|
|
|
|
|
|
|
void drop_channel_participant_cache(ChannelId channel_id);
|
|
|
|
|
2024-02-26 12:14:54 +01:00
|
|
|
struct CanTransferOwnershipResult {
|
|
|
|
enum class Type : uint8 { Ok, PasswordNeeded, PasswordTooFresh, SessionTooFresh };
|
|
|
|
Type type = Type::Ok;
|
|
|
|
int32 retry_after = 0;
|
|
|
|
};
|
|
|
|
void can_transfer_ownership(Promise<CanTransferOwnershipResult> &&promise);
|
|
|
|
|
|
|
|
static td_api::object_ptr<td_api::CanTransferOwnershipResult> get_can_transfer_ownership_result_object(
|
|
|
|
CanTransferOwnershipResult result);
|
|
|
|
|
|
|
|
void transfer_dialog_ownership(DialogId dialog_id, UserId user_id, const string &password, Promise<Unit> &&promise);
|
|
|
|
|
2024-01-04 16:30:12 +01:00
|
|
|
void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
|
|
|
|
|
2024-01-04 16:02:36 +01:00
|
|
|
private:
|
2024-02-19 00:15:05 +01:00
|
|
|
static constexpr int32 ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME = 30 * 60;
|
2024-01-04 16:02:36 +01:00
|
|
|
|
2024-01-04 16:30:12 +01:00
|
|
|
static constexpr int32 ONLINE_MEMBER_COUNT_UPDATE_TIME = 5 * 60;
|
|
|
|
|
2024-01-08 16:03:04 +01:00
|
|
|
static constexpr int32 CHANNEL_PARTICIPANT_CACHE_TIME = 1800; // some reasonable limit
|
|
|
|
|
2024-03-02 23:22:49 +01:00
|
|
|
static constexpr int32 MAX_GET_CHANNEL_PARTICIPANTS = 200; // server side limit
|
|
|
|
|
2024-02-19 00:15:05 +01:00
|
|
|
void tear_down() final;
|
|
|
|
|
2024-01-08 12:22:44 +01:00
|
|
|
static void on_update_dialog_online_member_count_timeout_callback(void *dialog_participant_manager_ptr,
|
2024-01-08 12:15:15 +01:00
|
|
|
int64 dialog_id_int);
|
2024-01-04 16:30:12 +01:00
|
|
|
|
|
|
|
void set_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server,
|
|
|
|
const char *source);
|
|
|
|
|
|
|
|
void on_update_dialog_online_member_count_timeout(DialogId dialog_id);
|
|
|
|
|
|
|
|
void send_update_chat_online_member_count(DialogId dialog_id, int32 online_member_count) const;
|
|
|
|
|
2024-01-08 12:36:18 +01:00
|
|
|
Status can_manage_dialog_join_requests(DialogId dialog_id);
|
|
|
|
|
2024-01-08 13:23:16 +01:00
|
|
|
td_api::object_ptr<td_api::chatAdministrators> get_chat_administrators_object(
|
|
|
|
const vector<DialogAdministrator> &dialog_administrators);
|
|
|
|
|
|
|
|
static string get_dialog_administrators_database_key(DialogId dialog_id);
|
|
|
|
|
|
|
|
void on_load_dialog_administrators_from_database(DialogId dialog_id, string value,
|
|
|
|
Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
|
|
|
|
|
|
|
|
void on_load_administrator_users_finished(DialogId dialog_id, vector<DialogAdministrator> administrators,
|
|
|
|
Result<Unit> result,
|
|
|
|
Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
|
|
|
|
|
|
|
|
void on_reload_dialog_administrators(DialogId dialog_id,
|
|
|
|
Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
|
|
|
|
|
2024-01-08 15:27:32 +01:00
|
|
|
void send_update_chat_member(DialogId dialog_id, UserId agent_user_id, int32 date,
|
2024-04-29 14:51:42 +02:00
|
|
|
const DialogInviteLink &invite_link, bool via_join_request,
|
|
|
|
bool via_dialog_filter_invite_link, const DialogParticipant &old_dialog_participant,
|
2024-01-08 15:27:32 +01:00
|
|
|
const DialogParticipant &new_dialog_participant);
|
|
|
|
|
2024-01-08 15:35:24 +01:00
|
|
|
void do_get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id,
|
|
|
|
Promise<DialogParticipant> &&promise);
|
|
|
|
|
|
|
|
void finish_get_dialog_participant(DialogParticipant &&dialog_participant,
|
|
|
|
Promise<td_api::object_ptr<td_api::chatMember>> &&promise);
|
|
|
|
|
2024-04-29 15:28:18 +02:00
|
|
|
void finish_get_channel_participant(ChannelId channel_id, DialogId participant_dialog_id,
|
|
|
|
DialogParticipant &&dialog_participant, Promise<DialogParticipant> &&promise);
|
2024-01-08 15:45:04 +01:00
|
|
|
|
2024-03-02 23:43:42 +01:00
|
|
|
std::pair<int32, vector<DialogId>> search_among_dialogs(const vector<DialogId> &dialog_ids, const string &query,
|
|
|
|
int32 limit) const;
|
|
|
|
|
2024-03-02 00:09:56 +01:00
|
|
|
DialogParticipants search_private_chat_participants(UserId peer_user_id, const string &query, int32 limit,
|
|
|
|
DialogParticipantFilter filter) const;
|
2024-03-02 00:02:01 +01:00
|
|
|
|
2024-03-02 01:02:34 +01:00
|
|
|
void search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantFilter filter,
|
|
|
|
Promise<DialogParticipants> &&promise);
|
|
|
|
|
|
|
|
void do_search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantFilter filter,
|
|
|
|
Promise<DialogParticipants> &&promise);
|
|
|
|
|
2024-03-02 23:22:49 +01:00
|
|
|
void on_get_channel_participants(
|
|
|
|
ChannelId channel_id, ChannelParticipantFilter &&filter, int32 offset, int32 limit, string additional_query,
|
|
|
|
int32 additional_limit,
|
|
|
|
telegram_api::object_ptr<telegram_api::channels_channelParticipants> &&channel_participants,
|
|
|
|
Promise<DialogParticipants> &&promise);
|
|
|
|
|
2024-02-24 22:45:19 +01:00
|
|
|
void set_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status, bool is_recursive,
|
|
|
|
Promise<Unit> &&promise);
|
|
|
|
|
2024-04-09 16:01:51 +02:00
|
|
|
void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit,
|
|
|
|
Promise<td_api::object_ptr<td_api::failedToAddMembers>> &&promise);
|
2024-02-24 22:45:19 +01:00
|
|
|
|
|
|
|
void send_edit_chat_admin_query(ChatId chat_id, UserId user_id, bool is_administrator, Promise<Unit> &&promise);
|
|
|
|
|
|
|
|
void delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, Promise<Unit> &&promise);
|
|
|
|
|
2024-01-08 16:44:13 +01:00
|
|
|
void add_channel_participant(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &old_status,
|
2024-04-09 16:01:51 +02:00
|
|
|
Promise<td_api::object_ptr<td_api::failedToAddMembers>> &&promise);
|
2024-01-08 16:44:13 +01:00
|
|
|
|
2024-05-03 14:58:15 +02:00
|
|
|
void on_join_channel(ChannelId channel_id, bool was_speculatively_updated, DialogParticipantStatus &&old_status,
|
|
|
|
DialogParticipantStatus &&new_status, Result<Unit> &&result);
|
2024-01-10 15:42:14 +01:00
|
|
|
|
2024-04-09 16:01:51 +02:00
|
|
|
void add_channel_participants(ChannelId channel_id, const vector<UserId> &user_ids,
|
|
|
|
Promise<td_api::object_ptr<td_api::failedToAddMembers>> &&promise);
|
2024-01-08 16:44:13 +01:00
|
|
|
|
|
|
|
void set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id,
|
|
|
|
td_api::object_ptr<td_api::ChatMemberStatus> &&chat_member_status,
|
|
|
|
Promise<Unit> &&promise);
|
|
|
|
|
|
|
|
void set_channel_participant_status_impl(ChannelId channel_id, DialogId participant_dialog_id,
|
|
|
|
DialogParticipantStatus new_status, DialogParticipantStatus old_status,
|
|
|
|
Promise<Unit> &&promise);
|
|
|
|
|
|
|
|
void promote_channel_participant(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &new_status,
|
|
|
|
const DialogParticipantStatus &old_status, Promise<Unit> &&promise);
|
|
|
|
|
|
|
|
void restrict_channel_participant(ChannelId channel_id, DialogId participant_dialog_id,
|
|
|
|
DialogParticipantStatus &&new_status, DialogParticipantStatus &&old_status,
|
|
|
|
Promise<Unit> &&promise);
|
|
|
|
|
2024-01-08 16:50:18 +01:00
|
|
|
void speculative_add_channel_user(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &new_status,
|
|
|
|
const DialogParticipantStatus &old_status);
|
|
|
|
|
2024-01-08 16:03:04 +01:00
|
|
|
void update_channel_participant_status_cache(ChannelId channel_id, DialogId participant_dialog_id,
|
|
|
|
DialogParticipantStatus &&dialog_participant_status);
|
|
|
|
|
|
|
|
const DialogParticipant *get_channel_participant_from_cache(ChannelId channel_id, DialogId participant_dialog_id);
|
|
|
|
|
|
|
|
static void on_channel_participant_cache_timeout_callback(void *dialog_participant_manager_ptr,
|
|
|
|
int64 channel_id_long);
|
|
|
|
|
|
|
|
void on_channel_participant_cache_timeout(ChannelId channel_id);
|
|
|
|
|
2024-03-04 01:16:20 +01:00
|
|
|
void set_cached_channel_participants(ChannelId channel_id, vector<DialogParticipant> participants);
|
|
|
|
|
|
|
|
void drop_cached_channel_participants(ChannelId channel_id);
|
|
|
|
|
|
|
|
void update_channel_online_member_count(ChannelId channel_id, bool is_from_server);
|
|
|
|
|
2024-02-26 12:14:54 +01:00
|
|
|
void transfer_channel_ownership(ChannelId channel_id, UserId user_id,
|
|
|
|
tl_object_ptr<telegram_api::InputCheckPasswordSRP> input_check_password,
|
|
|
|
Promise<Unit> &&promise);
|
|
|
|
|
2024-01-04 16:30:12 +01:00
|
|
|
struct OnlineMemberCountInfo {
|
|
|
|
int32 online_member_count = 0;
|
|
|
|
double update_time = 0;
|
|
|
|
bool is_update_sent = false;
|
|
|
|
};
|
|
|
|
FlatHashMap<DialogId, OnlineMemberCountInfo, DialogIdHash> dialog_online_member_counts_;
|
|
|
|
|
2024-02-19 00:15:05 +01:00
|
|
|
struct UserOnlineMemberDialogs {
|
|
|
|
FlatHashMap<DialogId, int32, DialogIdHash> online_member_dialogs_; // dialog_id -> time
|
|
|
|
};
|
|
|
|
FlatHashMap<UserId, unique_ptr<UserOnlineMemberDialogs>, UserIdHash> user_online_member_dialogs_;
|
|
|
|
|
2024-01-08 13:23:16 +01:00
|
|
|
FlatHashMap<DialogId, vector<DialogAdministrator>, DialogIdHash> dialog_administrators_;
|
|
|
|
|
2024-01-08 16:03:04 +01:00
|
|
|
// bot-administrators only
|
|
|
|
struct ChannelParticipantInfo {
|
|
|
|
DialogParticipant participant_;
|
|
|
|
|
|
|
|
int32 last_access_date_ = 0;
|
|
|
|
};
|
|
|
|
struct ChannelParticipants {
|
|
|
|
FlatHashMap<DialogId, ChannelParticipantInfo, DialogIdHash> participants_;
|
|
|
|
};
|
|
|
|
FlatHashMap<ChannelId, ChannelParticipants, ChannelIdHash> channel_participants_;
|
|
|
|
|
2024-03-04 01:16:20 +01:00
|
|
|
FlatHashMap<ChannelId, vector<DialogParticipant>, ChannelIdHash> cached_channel_participants_;
|
|
|
|
|
2024-04-09 16:01:51 +02:00
|
|
|
FlatHashMap<ChannelId, vector<Promise<td_api::object_ptr<td_api::failedToAddMembers>>>, ChannelIdHash>
|
|
|
|
join_channel_queries_;
|
2024-01-10 15:42:14 +01:00
|
|
|
|
2024-01-08 16:03:04 +01:00
|
|
|
MultiTimeout update_dialog_online_member_count_timeout_{"UpdateDialogOnlineMemberCountTimeout"};
|
|
|
|
MultiTimeout channel_participant_cache_timeout_{"ChannelParticipantCacheTimeout"};
|
|
|
|
|
2024-01-04 16:02:36 +01:00
|
|
|
Td *td_;
|
|
|
|
ActorShared<> parent_;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace td
|