Support sending and editing stories in channels.

This commit is contained in:
levlam 2023-09-04 23:49:17 +03:00
parent 58097b847b
commit b16de41ef1
6 changed files with 166 additions and 108 deletions

View File

@ -6994,7 +6994,7 @@ setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok;
//@limit The maximum number of voters to be returned; must be positive and can't be greater than 50. For optimal performance, the number of returned voters is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached //@limit The maximum number of voters to be returned; must be positive and can't be greater than 50. For optimal performance, the number of returned voters is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached
getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = MessageSenders; getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = MessageSenders;
//@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set //@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag is set
//@chat_id Identifier of the chat to which the poll belongs //@chat_id Identifier of the chat to which the poll belongs
//@message_id Identifier of the message containing the poll //@message_id Identifier of the message containing the poll
//@reply_markup The new message reply markup; pass null if none; for bots only //@reply_markup The new message reply markup; pass null if none; for bots only
@ -7522,10 +7522,11 @@ readChatList chat_list:ChatList = Ok;
//@only_local Pass true to get only locally available information without sending network requests //@only_local Pass true to get only locally available information without sending network requests
getStory story_sender_chat_id:int53 story_id:int32 only_local:Bool = Story; getStory story_sender_chat_id:int53 story_id:int32 only_local:Bool = Story;
//@description Checks whether the current user can send a story //@description Checks whether the current user can send a story on behalf of a chat; requires can_post_stories rights for channel chats @chat_id Chat identifier
canSendStory = CanSendStoryResult; canSendStory chat_id:int53 = CanSendStoryResult;
//@description Sends a new story. Returns a temporary story //@description Sends a new story to a chat; requires can_post_stories rights for channel chats. Returns a temporary story
//@chat_id Identifier of the chat that will post the story
//@content Content of the story //@content Content of the story
//@areas Clickable rectangle areas to be shown on the story media; pass null if none //@areas Clickable rectangle areas to be shown on the story media; pass null if none
//@caption Story caption; pass null to use an empty caption; 0-getOption("story_caption_length_max") characters //@caption Story caption; pass null to use an empty caption; 0-getOption("story_caption_length_max") characters
@ -7533,23 +7534,32 @@ canSendStory = CanSendStoryResult;
//@active_period Period after which the story is moved to archive, in seconds; must be one of 6 * 3600, 12 * 3600, 86400, or 2 * 86400 for Telegram Premium users, and 86400 otherwise //@active_period Period after which the story is moved to archive, in seconds; must be one of 6 * 3600, 12 * 3600, 86400, or 2 * 86400 for Telegram Premium users, and 86400 otherwise
//@is_pinned Pass true to keep the story accessible after expiration //@is_pinned Pass true to keep the story accessible after expiration
//@protect_content Pass true if the content of the story must be protected from forwarding and screenshotting //@protect_content Pass true if the content of the story must be protected from forwarding and screenshotting
sendStory content:InputStoryContent areas:inputStoryAreas caption:formattedText privacy_settings:StoryPrivacySettings active_period:int32 is_pinned:Bool protect_content:Bool = Story; sendStory chat_id:int53 content:InputStoryContent areas:inputStoryAreas caption:formattedText privacy_settings:StoryPrivacySettings active_period:int32 is_pinned:Bool protect_content:Bool = Story;
//@description Changes content and caption of a previously sent story //@description Changes content and caption of a story. Can be called only if story.can_be_edited == true
//@story_sender_chat_id Identifier of the chat that posted the story
//@story_id Identifier of the story to edit //@story_id Identifier of the story to edit
//@content New content of the story; pass null to keep the current content //@content New content of the story; pass null to keep the current content
//@areas New clickable rectangle areas to be shown on the story media; pass null to keep the current areas. Areas can't be edited if story content isn't changed //@areas New clickable rectangle areas to be shown on the story media; pass null to keep the current areas. Areas can't be edited if story content isn't changed
//@caption New story caption; pass null to keep the current caption //@caption New story caption; pass null to keep the current caption
editStory story_id:int32 content:InputStoryContent areas:inputStoryAreas caption:formattedText = Ok; editStory story_sender_chat_id:int53 story_id:int32 content:InputStoryContent areas:inputStoryAreas caption:formattedText = Ok;
//@description Changes privacy settings of a previously sent story @story_id Identifier of the story @privacy_settings The new privacy settigs for the story //@description Changes privacy settings of a story. Can be called only if story.can_be_edited == true
setStoryPrivacySettings story_id:int32 privacy_settings:StoryPrivacySettings = Ok; //@story_sender_chat_id Identifier of the chat that posted the story
//@story_id Identifier of the story
//@privacy_settings The new privacy settigs for the story
setStoryPrivacySettings story_sender_chat_id:int53 story_id:int32 privacy_settings:StoryPrivacySettings = Ok;
//@description Toggles whether a story is accessible after expiration @story_id Identifier of the story @is_pinned Pass true to make the story accessible after expiration; pass false to make it private //@description Toggles whether a story is accessible after expiration. Can be called only if story.can_toggle_is_pinned == true
toggleStoryIsPinned story_id:int32 is_pinned:Bool = Ok; //@story_sender_chat_id Identifier of the chat that posted the story
//@story_id Identifier of the story
//@is_pinned Pass true to make the story accessible after expiration; pass false to make it private
toggleStoryIsPinned story_sender_chat_id:int53 story_id:int32 is_pinned:Bool = Ok;
//@description Deletes a previously sent story @story_id Identifier of the story to delete //@description Deletes a previously sent story. Can be called only if story.can_be_deleted == true
deleteStory story_id:int32 = Ok; //@story_sender_chat_id Identifier of the chat that posted the story
//@story_id Identifier of the story to delete
deleteStory story_sender_chat_id:int53 story_id:int32 = Ok;
//@description Returns list of chats with non-default notification settings for stories //@description Returns list of chats with non-default notification settings for stories
getStoryNotificationSettingsExceptions = Chats; getStoryNotificationSettingsExceptions = Chats;
@ -7573,12 +7583,13 @@ getChatActiveStories chat_id:int53 = ChatActiveStories;
//-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit //-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit
getChatPinnedStories chat_id:int53 from_story_id:int32 limit:int32 = Stories; getChatPinnedStories chat_id:int53 from_story_id:int32 limit:int32 = Stories;
//@description Returns the list of all stories of the current user. The stories are returned in a reverse chronological order (i.e., in order of decreasing story_id). //@description Returns the list of all stories posted by the given chat; requires can_edit_stories rights for channel chats.
//-For optimal performance, the number of returned stories is chosen by TDLib //-The stories are returned in a reverse chronological order (i.e., in order of decreasing story_id). For optimal performance, the number of returned stories is chosen by TDLib
//@chat_id Chat identifier
//@from_story_id Identifier of the story starting from which stories must be returned; use 0 to get results from the last story //@from_story_id Identifier of the story starting from which stories must be returned; use 0 to get results from the last story
//@limit The maximum number of stories to be returned //@limit The maximum number of stories to be returned
//-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit //-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit
getArchivedStories from_story_id:int32 limit:int32 = Stories; getChatArchivedStories chat_id:int53 from_story_id:int32 limit:int32 = Stories;
//@description Informs TDLib that a story is opened and is being viewed by the user //@description Informs TDLib that a story is opened and is being viewed by the user
//@story_sender_chat_id The identifier of the sender of the opened story //@story_sender_chat_id The identifier of the sender of the opened story
@ -7600,7 +7611,7 @@ getStoryAvailableReactions row_size:int32 = AvailableReactions;
//@update_recent_reactions Pass true if the reaction needs to be added to recent reactions //@update_recent_reactions Pass true if the reaction needs to be added to recent reactions
setStoryReaction story_sender_chat_id:int53 story_id:int32 reaction_type:ReactionType update_recent_reactions:Bool = Ok; setStoryReaction story_sender_chat_id:int53 story_id:int32 reaction_type:ReactionType update_recent_reactions:Bool = Ok;
//@description Returns viewers of a story. The method can be called if story.can_get_viewers == true //@description Returns viewers of a story. The method can be called only for stories posted on behalf of the current user
//@story_id Story identifier //@story_id Story identifier
//@query Query to search for in names and usernames of the viewers; may be empty to get all relevant viewers //@query Query to search for in names and usernames of the viewers; may be empty to get all relevant viewers
//@only_contacts Pass true to get only contacts; pass false to get all relevant viewers //@only_contacts Pass true to get only contacts; pass false to get all relevant viewers

View File

@ -664,7 +664,7 @@ class GetStoriesViewsQuery final : public Td::ResultHandler {
auto ptr = result_ptr.move_as_ok(); auto ptr = result_ptr.move_as_ok();
LOG(DEBUG) << "Receive result for GetStoriesViewsQuery: " << to_string(ptr); LOG(DEBUG) << "Receive result for GetStoriesViewsQuery: " << to_string(ptr);
td_->story_manager_->on_get_story_views(story_ids_, std::move(ptr)); td_->story_manager_->on_get_story_views(dialog_id_, story_ids_, std::move(ptr));
} }
void on_error(Status status) final { void on_error(Status status) final {
@ -1502,6 +1502,27 @@ bool StoryManager::can_delete_stories(DialogId owner_dialog_id) const {
} }
} }
bool StoryManager::can_edit_story(StoryFullId story_full_id, const Story *story) {
if (!story_full_id.get_story_id().is_server()) {
return false;
}
return can_edit_stories(story_full_id.get_dialog_id());
}
bool StoryManager::can_toggle_story_is_pinned(StoryFullId story_full_id, const Story *story) {
if (!story_full_id.get_story_id().is_server()) {
return false;
}
return can_edit_stories(story_full_id.get_dialog_id());
}
bool StoryManager::can_delete_story(StoryFullId story_full_id, const Story *story) {
if (!story_full_id.get_story_id().is_server()) {
return true;
}
return can_delete_stories(story_full_id.get_dialog_id());
}
bool StoryManager::is_active_story(const Story *story) { bool StoryManager::is_active_story(const Story *story) {
return story != nullptr && G()->unix_time() < story->expire_date_; return story != nullptr && G()->unix_time() < story->expire_date_;
} }
@ -2122,17 +2143,18 @@ void StoryManager::on_get_dialog_pinned_stories(DialogId owner_dialog_id,
}))); })));
} }
void StoryManager::get_story_archive(StoryId from_story_id, int32 limit, void StoryManager::get_story_archive(DialogId owner_dialog_id, StoryId from_story_id, int32 limit,
Promise<td_api::object_ptr<td_api::stories>> &&promise) { Promise<td_api::object_ptr<td_api::stories>> &&promise) {
if (limit <= 0) { if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive")); return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
} }
if (from_story_id != StoryId() && !from_story_id.is_server()) { if (from_story_id != StoryId() && !from_story_id.is_server()) {
return promise.set_error(Status::Error(400, "Invalid value of parameter from_story_id specified")); return promise.set_error(Status::Error(400, "Invalid value of parameter from_story_id specified"));
} }
if (!can_edit_stories(owner_dialog_id)) {
return promise.set_error(Status::Error(400, "Can't get story archive in the chat"));
}
DialogId owner_dialog_id(td_->contacts_manager_->get_my_id());
auto query_promise = auto query_promise =
PromiseCreator::lambda([actor_id = actor_id(this), owner_dialog_id, promise = std::move(promise)]( PromiseCreator::lambda([actor_id = actor_id(this), owner_dialog_id, promise = std::move(promise)](
Result<telegram_api::object_ptr<telegram_api::stories_stories>> &&result) mutable { Result<telegram_api::object_ptr<telegram_api::stories_stories>> &&result) mutable {
@ -3900,7 +3922,7 @@ bool StoryManager::is_subscribed_to_dialog_stories(DialogId owner_dialog_id) con
} }
switch (owner_dialog_id.get_type()) { switch (owner_dialog_id.get_type()) {
case DialogType::User: case DialogType::User:
if (owner_dialog_id == DialogId(td_->contacts_manager_->get_my_id())) { if (is_my_story(owner_dialog_id)) {
return true; return true;
} }
return td_->contacts_manager_->is_user_contact(owner_dialog_id.get_user_id()); return td_->contacts_manager_->is_user_contact(owner_dialog_id.get_user_id());
@ -3919,7 +3941,7 @@ StoryListId StoryManager::get_dialog_story_list_id(DialogId owner_dialog_id) con
} }
switch (owner_dialog_id.get_type()) { switch (owner_dialog_id.get_type()) {
case DialogType::User: case DialogType::User:
if (owner_dialog_id != DialogId(td_->contacts_manager_->get_my_id()) && if (!is_my_story(owner_dialog_id) &&
td_->contacts_manager_->get_user_stories_hidden(owner_dialog_id.get_user_id())) { td_->contacts_manager_->get_user_stories_hidden(owner_dialog_id.get_user_id())) {
return StoryListId::archive(); return StoryListId::archive();
} }
@ -3947,7 +3969,7 @@ void StoryManager::on_dialog_active_stories_order_updated(DialogId owner_dialog_
} }
} }
void StoryManager::on_get_story_views(const vector<StoryId> &story_ids, void StoryManager::on_get_story_views(DialogId owner_dialog_id, const vector<StoryId> &story_ids,
telegram_api::object_ptr<telegram_api::stories_storyViews> &&story_views) { telegram_api::object_ptr<telegram_api::stories_storyViews> &&story_views) {
schedule_interaction_info_update(); schedule_interaction_info_update();
td_->contacts_manager_->on_get_users(std::move(story_views->users_), "on_get_story_views"); td_->contacts_manager_->on_get_users(std::move(story_views->users_), "on_get_story_views");
@ -3955,7 +3977,6 @@ void StoryManager::on_get_story_views(const vector<StoryId> &story_ids,
LOG(ERROR) << "Receive invalid views for " << story_ids << ": " << to_string(story_views); LOG(ERROR) << "Receive invalid views for " << story_ids << ": " << to_string(story_views);
return; return;
} }
DialogId owner_dialog_id(td_->contacts_manager_->get_my_id());
for (size_t i = 0; i < story_ids.size(); i++) { for (size_t i = 0; i < story_ids.size(); i++) {
auto story_id = story_ids[i]; auto story_id = story_ids[i];
CHECK(story_id.is_server()); CHECK(story_id.is_server());
@ -4094,19 +4115,25 @@ Result<StoryId> StoryManager::get_next_yet_unsent_story_id(DialogId dialog_id) {
return StoryId(++story_id); return StoryId(++story_id);
} }
void StoryManager::can_send_story(Promise<td_api::object_ptr<td_api::CanSendStoryResult>> &&promise) { void StoryManager::can_send_story(DialogId dialog_id,
DialogId dialog_id(td_->contacts_manager_->get_my_id()); Promise<td_api::object_ptr<td_api::CanSendStoryResult>> &&promise) {
if (!can_post_stories(dialog_id)) {
return promise.set_error(Status::Error(400, "Not enough rights to post stories in the chat"));
}
td_->create_handler<CanSendStoryQuery>(std::move(promise))->send(dialog_id); td_->create_handler<CanSendStoryQuery>(std::move(promise))->send(dialog_id);
} }
void StoryManager::send_story(td_api::object_ptr<td_api::InputStoryContent> &&input_story_content, void StoryManager::send_story(DialogId dialog_id, td_api::object_ptr<td_api::InputStoryContent> &&input_story_content,
td_api::object_ptr<td_api::inputStoryAreas> &&input_areas, td_api::object_ptr<td_api::inputStoryAreas> &&input_areas,
td_api::object_ptr<td_api::formattedText> &&input_caption, td_api::object_ptr<td_api::formattedText> &&input_caption,
td_api::object_ptr<td_api::StoryPrivacySettings> &&settings, int32 active_period, td_api::object_ptr<td_api::StoryPrivacySettings> &&settings, int32 active_period,
bool is_pinned, bool protect_content, bool is_pinned, bool protect_content,
Promise<td_api::object_ptr<td_api::story>> &&promise) { Promise<td_api::object_ptr<td_api::story>> &&promise) {
if (!can_post_stories(dialog_id)) {
return promise.set_error(Status::Error(400, "Not enough rights to post stories in the chat"));
}
bool is_bot = td_->auth_manager_->is_bot(); bool is_bot = td_->auth_manager_->is_bot();
DialogId dialog_id(td_->contacts_manager_->get_my_id());
TRY_RESULT_PROMISE(promise, content, get_input_story_content(td_, std::move(input_story_content), dialog_id)); TRY_RESULT_PROMISE(promise, content, get_input_story_content(td_, std::move(input_story_content), dialog_id));
TRY_RESULT_PROMISE(promise, caption, TRY_RESULT_PROMISE(promise, caption,
get_formatted_text(td_, DialogId(), std::move(input_caption), is_bot, true, false, false)); get_formatted_text(td_, DialogId(), std::move(input_caption), is_bot, true, false, false));
@ -4427,16 +4454,16 @@ class StoryManager::EditStoryLogEvent {
} }
}; };
void StoryManager::edit_story(StoryId story_id, td_api::object_ptr<td_api::InputStoryContent> &&input_story_content, void StoryManager::edit_story(DialogId owner_dialog_id, StoryId story_id,
td_api::object_ptr<td_api::InputStoryContent> &&input_story_content,
td_api::object_ptr<td_api::inputStoryAreas> &&input_areas, td_api::object_ptr<td_api::inputStoryAreas> &&input_areas,
td_api::object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise) { td_api::object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise) {
DialogId dialog_id(td_->contacts_manager_->get_my_id()); StoryFullId story_full_id{owner_dialog_id, story_id};
StoryFullId story_full_id{dialog_id, story_id};
const Story *story = get_story(story_full_id); const Story *story = get_story(story_full_id);
if (story == nullptr || story->content_ == nullptr) { if (story == nullptr || story->content_ == nullptr) {
return promise.set_error(Status::Error(400, "Story not found")); return promise.set_error(Status::Error(400, "Story not found"));
} }
if (!story_id.is_server()) { if (!can_edit_story(story_full_id, story)) {
return promise.set_error(Status::Error(400, "Story can't be edited")); return promise.set_error(Status::Error(400, "Story can't be edited"));
} }
@ -4448,7 +4475,7 @@ void StoryManager::edit_story(StoryId story_id, td_api::object_ptr<td_api::Input
FormattedText caption; FormattedText caption;
if (input_story_content != nullptr) { if (input_story_content != nullptr) {
TRY_RESULT_PROMISE_ASSIGN(promise, content, TRY_RESULT_PROMISE_ASSIGN(promise, content,
get_input_story_content(td_, std::move(input_story_content), dialog_id)); get_input_story_content(td_, std::move(input_story_content), owner_dialog_id));
} }
if (are_media_areas_edited) { if (are_media_areas_edited) {
for (auto &input_area : input_areas->areas_) { for (auto &input_area : input_areas->areas_) {
@ -4511,9 +4538,9 @@ void StoryManager::edit_story(StoryId story_id, td_api::object_ptr<td_api::Input
auto new_story = make_unique<Story>(); auto new_story = make_unique<Story>();
new_story->content_ = copy_story_content(edited_story->content_.get()); new_story->content_ = copy_story_content(edited_story->content_.get());
auto pending_story = auto pending_story = td::make_unique<PendingStory>(owner_dialog_id, story_id,
td::make_unique<PendingStory>(dialog_id, story_id, std::numeric_limits<uint32>::max() - (++send_story_count_), std::numeric_limits<uint32>::max() - (++send_story_count_),
edit_generation, std::move(new_story)); edit_generation, std::move(new_story));
if (G()->use_message_database()) { if (G()->use_message_database()) {
EditStoryLogEvent log_event(pending_story.get(), edited_story->edit_media_areas_, edited_story->areas_, EditStoryLogEvent log_event(pending_story.get(), edited_story->edit_media_areas_, edited_story->areas_,
edited_story->edit_caption_, edited_story->caption_); edited_story->edit_caption_, edited_story->caption_);
@ -4633,62 +4660,62 @@ void StoryManager::delete_pending_story(FileId file_id, unique_ptr<PendingStory>
} }
} }
void StoryManager::set_story_privacy_settings(StoryId story_id, void StoryManager::set_story_privacy_settings(DialogId owner_dialog_id, StoryId story_id,
td_api::object_ptr<td_api::StoryPrivacySettings> &&settings, td_api::object_ptr<td_api::StoryPrivacySettings> &&settings,
Promise<Unit> &&promise) { Promise<Unit> &&promise) {
DialogId dialog_id(td_->contacts_manager_->get_my_id()); StoryFullId story_full_id{owner_dialog_id, story_id};
const Story *story = get_story({dialog_id, story_id}); const Story *story = get_story(story_full_id);
if (story == nullptr || story->content_ == nullptr) { if (story == nullptr || story->content_ == nullptr) {
return promise.set_error(Status::Error(400, "Story not found")); return promise.set_error(Status::Error(400, "Story not found"));
} }
if (!story_id.is_server()) { if (!can_edit_story(story_full_id, story)) {
return promise.set_error(Status::Error(400, "Story privacy settings can't be edited")); return promise.set_error(Status::Error(400, "Story privacy settings can't be edited"));
} }
TRY_RESULT_PROMISE(promise, privacy_rules, TRY_RESULT_PROMISE(promise, privacy_rules,
UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(settings))); UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(settings)));
td_->create_handler<EditStoryPrivacyQuery>(std::move(promise))->send(dialog_id, story_id, std::move(privacy_rules)); td_->create_handler<EditStoryPrivacyQuery>(std::move(promise))
->send(owner_dialog_id, story_id, std::move(privacy_rules));
} }
void StoryManager::toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise<Unit> &&promise) { void StoryManager::toggle_story_is_pinned(DialogId owner_dialog_id, StoryId story_id, bool is_pinned,
DialogId dialog_id(td_->contacts_manager_->get_my_id()); Promise<Unit> &&promise) {
const Story *story = get_story({dialog_id, story_id}); StoryFullId story_full_id{owner_dialog_id, story_id};
const Story *story = get_story(story_full_id);
if (story == nullptr || story->content_ == nullptr) { if (story == nullptr || story->content_ == nullptr) {
return promise.set_error(Status::Error(400, "Story not found")); return promise.set_error(Status::Error(400, "Story not found"));
} }
if (!story_id.is_server()) { if (!can_toggle_story_is_pinned(story_full_id, story)) {
return promise.set_error(Status::Error(400, "Story can't be pinned/unpinned")); return promise.set_error(Status::Error(400, "Story can't be pinned/unpinned"));
} }
auto query_promise = PromiseCreator::lambda( auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), story_full_id, is_pinned,
[actor_id = actor_id(this), story_id, is_pinned, promise = std::move(promise)](Result<Unit> &&result) mutable { promise = std::move(promise)](Result<Unit> &&result) mutable {
if (result.is_error()) { if (result.is_error()) {
return promise.set_error(result.move_as_error()); return promise.set_error(result.move_as_error());
} }
send_closure(actor_id, &StoryManager::on_toggle_story_is_pinned, story_id, is_pinned, std::move(promise)); send_closure(actor_id, &StoryManager::on_toggle_story_is_pinned, story_full_id, is_pinned, std::move(promise));
}); });
td_->create_handler<ToggleStoryPinnedQuery>(std::move(query_promise))->send(dialog_id, story_id, is_pinned); td_->create_handler<ToggleStoryPinnedQuery>(std::move(query_promise))->send(owner_dialog_id, story_id, is_pinned);
} }
void StoryManager::on_toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise<Unit> &&promise) { void StoryManager::on_toggle_story_is_pinned(StoryFullId story_full_id, bool is_pinned, Promise<Unit> &&promise) {
TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_STATUS_PROMISE(promise, G()->close_status());
DialogId dialog_id(td_->contacts_manager_->get_my_id()); Story *story = get_story_editable(story_full_id);
Story *story = get_story_editable({dialog_id, story_id});
if (story != nullptr) { if (story != nullptr) {
CHECK(story->content_ != nullptr); CHECK(story->content_ != nullptr);
story->is_pinned_ = is_pinned; story->is_pinned_ = is_pinned;
on_story_changed({dialog_id, story_id}, story, true, true); on_story_changed(story_full_id, story, true, true);
} }
promise.set_value(Unit()); promise.set_value(Unit());
} }
void StoryManager::delete_story(StoryId story_id, Promise<Unit> &&promise) { void StoryManager::delete_story(DialogId owner_dialog_id, StoryId story_id, Promise<Unit> &&promise) {
DialogId owner_dialog_id(td_->contacts_manager_->get_my_id());
StoryFullId story_full_id{owner_dialog_id, story_id}; StoryFullId story_full_id{owner_dialog_id, story_id};
const Story *story = get_story(story_full_id); const Story *story = get_story(story_full_id);
if (story == nullptr) { if (story == nullptr) {
return promise.set_error(Status::Error(400, "Story not found")); return promise.set_error(Status::Error(400, "Story not found"));
} }
if (!story_id.is_valid()) { if (!can_delete_story(story_full_id, story)) {
return promise.set_error(Status::Error(400, "Invalid story identifier")); return promise.set_error(Status::Error(400, "Story can't be deleted"));
} }
if (!story_id.is_server()) { if (!story_id.is_server()) {
auto file_id_it = being_uploaded_file_ids_.find(story_full_id); auto file_id_it = being_uploaded_file_ids_.find(story_full_id);
@ -4813,11 +4840,6 @@ void StoryManager::on_binlog_events(vector<BinlogEvent> &&events) {
log_event_parse(log_event, event.get_data()).ensure(); log_event_parse(log_event, event.get_data()).ensure();
auto owner_dialog_id = log_event.story_full_id_.get_dialog_id(); auto owner_dialog_id = log_event.story_full_id_.get_dialog_id();
if (owner_dialog_id != DialogId(td_->contacts_manager_->get_my_id())) {
binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
td_->messages_manager_->have_dialog_force(owner_dialog_id, "DeleteStoryOnServerLogEvent"); td_->messages_manager_->have_dialog_force(owner_dialog_id, "DeleteStoryOnServerLogEvent");
delete_story_on_server(log_event.story_full_id_, event.id_, Auto()); delete_story_on_server(log_event.story_full_id_, event.id_, Auto());
break; break;

View File

@ -197,9 +197,9 @@ class StoryManager final : public Actor {
void get_story(DialogId owner_dialog_id, StoryId story_id, bool only_local, void get_story(DialogId owner_dialog_id, StoryId story_id, bool only_local,
Promise<td_api::object_ptr<td_api::story>> &&promise); Promise<td_api::object_ptr<td_api::story>> &&promise);
void can_send_story(Promise<td_api::object_ptr<td_api::CanSendStoryResult>> &&promise); void can_send_story(DialogId dialog_id, Promise<td_api::object_ptr<td_api::CanSendStoryResult>> &&promise);
void send_story(td_api::object_ptr<td_api::InputStoryContent> &&input_story_content, void send_story(DialogId dialog_id, td_api::object_ptr<td_api::InputStoryContent> &&input_story_content,
td_api::object_ptr<td_api::inputStoryAreas> &&input_areas, td_api::object_ptr<td_api::inputStoryAreas> &&input_areas,
td_api::object_ptr<td_api::formattedText> &&input_caption, td_api::object_ptr<td_api::formattedText> &&input_caption,
td_api::object_ptr<td_api::StoryPrivacySettings> &&settings, int32 active_period, bool is_pinned, td_api::object_ptr<td_api::StoryPrivacySettings> &&settings, int32 active_period, bool is_pinned,
@ -207,16 +207,17 @@ class StoryManager final : public Actor {
void on_send_story_file_parts_missing(unique_ptr<PendingStory> &&pending_story, vector<int> &&bad_parts); void on_send_story_file_parts_missing(unique_ptr<PendingStory> &&pending_story, vector<int> &&bad_parts);
void edit_story(StoryId story_id, td_api::object_ptr<td_api::InputStoryContent> &&input_story_content, void edit_story(DialogId owner_dialog_id, StoryId story_id,
td_api::object_ptr<td_api::InputStoryContent> &&input_story_content,
td_api::object_ptr<td_api::inputStoryAreas> &&input_areas, td_api::object_ptr<td_api::inputStoryAreas> &&input_areas,
td_api::object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise); td_api::object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise);
void set_story_privacy_settings(StoryId story_id, td_api::object_ptr<td_api::StoryPrivacySettings> &&settings, void set_story_privacy_settings(DialogId owner_dialog_id, StoryId story_id,
Promise<Unit> &&promise); td_api::object_ptr<td_api::StoryPrivacySettings> &&settings, Promise<Unit> &&promise);
void toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise<Unit> &&promise); void toggle_story_is_pinned(DialogId owner_dialog_id, StoryId story_id, bool is_pinned, Promise<Unit> &&promise);
void delete_story(StoryId story_id, Promise<Unit> &&promise); void delete_story(DialogId owner_dialog_id, StoryId story_id, Promise<Unit> &&promise);
void load_active_stories(StoryListId story_list_id, Promise<Unit> &&promise); void load_active_stories(StoryListId story_list_id, Promise<Unit> &&promise);
@ -229,7 +230,8 @@ class StoryManager final : public Actor {
void get_dialog_pinned_stories(DialogId owner_dialog_id, StoryId from_story_id, int32 limit, void get_dialog_pinned_stories(DialogId owner_dialog_id, StoryId from_story_id, int32 limit,
Promise<td_api::object_ptr<td_api::stories>> &&promise); Promise<td_api::object_ptr<td_api::stories>> &&promise);
void get_story_archive(StoryId from_story_id, int32 limit, Promise<td_api::object_ptr<td_api::stories>> &&promise); void get_story_archive(DialogId owner_dialog_id, StoryId from_story_id, int32 limit,
Promise<td_api::object_ptr<td_api::stories>> &&promise);
void get_dialog_expiring_stories(DialogId owner_dialog_id, void get_dialog_expiring_stories(DialogId owner_dialog_id,
Promise<td_api::object_ptr<td_api::chatActiveStories>> &&promise); Promise<td_api::object_ptr<td_api::chatActiveStories>> &&promise);
@ -281,7 +283,7 @@ class StoryManager final : public Actor {
Status can_get_story_viewers(StoryFullId story_full_id, const Story *story, bool ignore_premium) const; Status can_get_story_viewers(StoryFullId story_full_id, const Story *story, bool ignore_premium) const;
void on_get_story_views(const vector<StoryId> &story_ids, void on_get_story_views(DialogId owner_dialog_id, const vector<StoryId> &story_ids,
telegram_api::object_ptr<telegram_api::stories_storyViews> &&story_views); telegram_api::object_ptr<telegram_api::stories_storyViews> &&story_views);
bool have_story(StoryFullId story_full_id) const; bool have_story(StoryFullId story_full_id) const;
@ -362,6 +364,12 @@ class StoryManager final : public Actor {
bool can_delete_stories(DialogId owner_dialog_id) const; bool can_delete_stories(DialogId owner_dialog_id) const;
bool can_edit_story(StoryFullId story_full_id, const Story *story);
bool can_toggle_story_is_pinned(StoryFullId story_full_id, const Story *story);
bool can_delete_story(StoryFullId story_full_id, const Story *story);
int32 get_story_viewers_expire_date(const Story *story) const; int32 get_story_viewers_expire_date(const Story *story) const;
static bool is_active_story(const Story *story); static bool is_active_story(const Story *story);
@ -496,7 +504,7 @@ class StoryManager final : public Actor {
void do_edit_story(FileId file_id, unique_ptr<PendingStory> &&pending_story, void do_edit_story(FileId file_id, unique_ptr<PendingStory> &&pending_story,
telegram_api::object_ptr<telegram_api::InputFile> input_file); telegram_api::object_ptr<telegram_api::InputFile> input_file);
void on_toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise<Unit> &&promise); void on_toggle_story_is_pinned(StoryFullId story_full_id, bool is_pinned, Promise<Unit> &&promise);
void on_update_dialog_max_story_ids(DialogId owner_dialog_id, StoryId max_story_id, StoryId max_read_story_id); void on_update_dialog_max_story_ids(DialogId owner_dialog_id, StoryId max_story_id, StoryId max_read_story_id);

View File

@ -5682,41 +5682,43 @@ void Td::on_request(uint64 id, const td_api::getStory &request) {
void Td::on_request(uint64 id, const td_api::canSendStory &request) { void Td::on_request(uint64 id, const td_api::canSendStory &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_REQUEST_PROMISE(); CREATE_REQUEST_PROMISE();
story_manager_->can_send_story(std::move(promise)); story_manager_->can_send_story(DialogId(request.chat_id_), std::move(promise));
} }
void Td::on_request(uint64 id, td_api::sendStory &request) { void Td::on_request(uint64 id, td_api::sendStory &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_REQUEST_PROMISE(); CREATE_REQUEST_PROMISE();
story_manager_->send_story(std::move(request.content_), std::move(request.areas_), std::move(request.caption_), story_manager_->send_story(DialogId(request.chat_id_), std::move(request.content_), std::move(request.areas_),
std::move(request.privacy_settings_), request.active_period_, request.is_pinned_, std::move(request.caption_), std::move(request.privacy_settings_), request.active_period_,
request.protect_content_, std::move(promise)); request.is_pinned_, request.protect_content_, std::move(promise));
} }
void Td::on_request(uint64 id, td_api::editStory &request) { void Td::on_request(uint64 id, td_api::editStory &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE(); CREATE_OK_REQUEST_PROMISE();
story_manager_->edit_story(StoryId(request.story_id_), std::move(request.content_), std::move(request.areas_), story_manager_->edit_story(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_),
std::move(request.caption_), std::move(promise)); std::move(request.content_), std::move(request.areas_), std::move(request.caption_),
std::move(promise));
} }
void Td::on_request(uint64 id, td_api::setStoryPrivacySettings &request) { void Td::on_request(uint64 id, td_api::setStoryPrivacySettings &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE(); CREATE_OK_REQUEST_PROMISE();
story_manager_->set_story_privacy_settings(StoryId(request.story_id_), std::move(request.privacy_settings_), story_manager_->set_story_privacy_settings(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_),
std::move(promise)); std::move(request.privacy_settings_), std::move(promise));
} }
void Td::on_request(uint64 id, const td_api::toggleStoryIsPinned &request) { void Td::on_request(uint64 id, const td_api::toggleStoryIsPinned &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE(); CREATE_OK_REQUEST_PROMISE();
story_manager_->toggle_story_is_pinned(StoryId(request.story_id_), request.is_pinned_, std::move(promise)); story_manager_->toggle_story_is_pinned(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_),
request.is_pinned_, std::move(promise));
} }
void Td::on_request(uint64 id, const td_api::deleteStory &request) { void Td::on_request(uint64 id, const td_api::deleteStory &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE(); CREATE_OK_REQUEST_PROMISE();
story_manager_->delete_story(StoryId(request.story_id_), std::move(promise)); story_manager_->delete_story(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_), std::move(promise));
} }
void Td::on_request(uint64 id, const td_api::loadActiveStories &request) { void Td::on_request(uint64 id, const td_api::loadActiveStories &request) {
@ -6521,10 +6523,11 @@ void Td::on_request(uint64 id, const td_api::getChatPinnedStories &request) {
std::move(promise)); std::move(promise));
} }
void Td::on_request(uint64 id, const td_api::getArchivedStories &request) { void Td::on_request(uint64 id, const td_api::getChatArchivedStories &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_REQUEST_PROMISE(); CREATE_REQUEST_PROMISE();
story_manager_->get_story_archive(StoryId(request.from_story_id_), request.limit_, std::move(promise)); story_manager_->get_story_archive(DialogId(request.chat_id_), StoryId(request.from_story_id_), request.limit_,
std::move(promise));
} }
void Td::on_request(uint64 id, const td_api::openStory &request) { void Td::on_request(uint64 id, const td_api::openStory &request) {

View File

@ -1022,7 +1022,7 @@ class Td final : public Actor {
void on_request(uint64 id, const td_api::getChatPinnedStories &request); void on_request(uint64 id, const td_api::getChatPinnedStories &request);
void on_request(uint64 id, const td_api::getArchivedStories &request); void on_request(uint64 id, const td_api::getChatArchivedStories &request);
void on_request(uint64 id, const td_api::openStory &request); void on_request(uint64 id, const td_api::openStory &request);

View File

@ -4113,8 +4113,11 @@ class CliClient final : public Actor {
get_args(args, story_sender_chat_id, story_id); get_args(args, story_sender_chat_id, story_id);
send_request(td_api::make_object<td_api::getStory>(story_sender_chat_id, story_id, op == "gstl")); send_request(td_api::make_object<td_api::getStory>(story_sender_chat_id, story_id, op == "gstl"));
} else if (op == "csst") { } else if (op == "csst") {
send_request(td_api::make_object<td_api::canSendStory>()); ChatId chat_id;
get_args(args, chat_id);
send_request(td_api::make_object<td_api::canSendStory>(chat_id));
} else if (op == "ssp" || op == "sspp") { } else if (op == "ssp" || op == "sspp") {
ChatId chat_id;
string photo; string photo;
string caption; string caption;
StoryPrivacySettings rules; StoryPrivacySettings rules;
@ -4122,12 +4125,14 @@ class CliClient final : public Actor {
int32 active_period; int32 active_period;
string sticker_file_ids; string sticker_file_ids;
bool protect_content; bool protect_content;
get_args(args, photo, caption, rules, areas, active_period, sticker_file_ids, protect_content); get_args(args, chat_id, photo, caption, rules, areas, active_period, sticker_file_ids, protect_content);
send_request(td_api::make_object<td_api::sendStory>( send_request(td_api::make_object<td_api::sendStory>(
chat_id,
td_api::make_object<td_api::inputStoryContentPhoto>(as_input_file(photo), td_api::make_object<td_api::inputStoryContentPhoto>(as_input_file(photo),
to_integers<int32>(sticker_file_ids)), to_integers<int32>(sticker_file_ids)),
areas, as_caption(caption), rules, active_period ? active_period : 86400, op == "sspp", protect_content)); areas, as_caption(caption), rules, active_period ? active_period : 86400, op == "sspp", protect_content));
} else if (op == "ssv" || op == "ssvp") { } else if (op == "ssv" || op == "ssvp") {
ChatId chat_id;
string video; string video;
string caption; string caption;
StoryPrivacySettings rules; StoryPrivacySettings rules;
@ -4136,56 +4141,64 @@ class CliClient final : public Actor {
double duration; double duration;
string sticker_file_ids; string sticker_file_ids;
bool protect_content; bool protect_content;
get_args(args, video, caption, areas, rules, active_period, duration, sticker_file_ids, protect_content); get_args(args, chat_id, video, caption, areas, rules, active_period, duration, sticker_file_ids, protect_content);
send_request(td_api::make_object<td_api::sendStory>( send_request(td_api::make_object<td_api::sendStory>(
chat_id,
td_api::make_object<td_api::inputStoryContentVideo>(as_input_file(video), td_api::make_object<td_api::inputStoryContentVideo>(as_input_file(video),
to_integers<int32>(sticker_file_ids), duration, true), to_integers<int32>(sticker_file_ids), duration, true),
areas, as_caption(caption), rules, active_period ? active_period : 86400, op == "ssvp", protect_content)); areas, as_caption(caption), rules, active_period ? active_period : 86400, op == "ssvp", protect_content));
} else if (op == "esc") { } else if (op == "esc") {
ChatId story_sender_chat_id;
StoryId story_id; StoryId story_id;
string caption; string caption;
InputStoryAreas areas; InputStoryAreas areas;
get_args(args, story_id, caption, areas); get_args(args, story_sender_chat_id, story_id, caption, areas);
send_request(td_api::make_object<td_api::editStory>(story_id, nullptr, areas, as_caption(caption))); send_request(
td_api::make_object<td_api::editStory>(story_sender_chat_id, story_id, nullptr, areas, as_caption(caption)));
} else if (op == "esp") { } else if (op == "esp") {
ChatId story_sender_chat_id;
StoryId story_id; StoryId story_id;
string photo; string photo;
string caption; string caption;
InputStoryAreas areas; InputStoryAreas areas;
string sticker_file_ids; string sticker_file_ids;
get_args(args, story_id, photo, caption, areas, sticker_file_ids); get_args(args, story_sender_chat_id, story_id, photo, caption, areas, sticker_file_ids);
send_request( send_request(
td_api::make_object<td_api::editStory>(story_id, td_api::make_object<td_api::editStory>(story_sender_chat_id, story_id,
td_api::make_object<td_api::inputStoryContentPhoto>( td_api::make_object<td_api::inputStoryContentPhoto>(
as_input_file(photo), to_integers<int32>(sticker_file_ids)), as_input_file(photo), to_integers<int32>(sticker_file_ids)),
areas, as_caption(caption))); areas, as_caption(caption)));
} else if (op == "esv") { } else if (op == "esv") {
ChatId story_sender_chat_id;
StoryId story_id; StoryId story_id;
string video; string video;
InputStoryAreas areas; InputStoryAreas areas;
string caption; string caption;
int32 duration; int32 duration;
string sticker_file_ids; string sticker_file_ids;
get_args(args, story_id, video, caption, duration, sticker_file_ids); get_args(args, story_sender_chat_id, story_id, video, caption, duration, sticker_file_ids);
send_request(td_api::make_object<td_api::editStory>( send_request(td_api::make_object<td_api::editStory>(
story_id, story_sender_chat_id, story_id,
td_api::make_object<td_api::inputStoryContentVideo>(as_input_file(video), td_api::make_object<td_api::inputStoryContentVideo>(as_input_file(video),
to_integers<int32>(sticker_file_ids), duration, false), to_integers<int32>(sticker_file_ids), duration, false),
areas, as_caption(caption))); areas, as_caption(caption)));
} else if (op == "ssps") { } else if (op == "ssps") {
ChatId story_sender_chat_id;
StoryId story_id; StoryId story_id;
StoryPrivacySettings rules; StoryPrivacySettings rules;
get_args(args, story_id, rules); get_args(args, story_sender_chat_id, story_id, rules);
send_request(td_api::make_object<td_api::setStoryPrivacySettings>(story_id, rules)); send_request(td_api::make_object<td_api::setStoryPrivacySettings>(story_sender_chat_id, story_id, rules));
} else if (op == "tsip") { } else if (op == "tsip") {
ChatId story_sender_chat_id;
StoryId story_id; StoryId story_id;
bool is_pinned; bool is_pinned;
get_args(args, story_id, is_pinned); get_args(args, story_sender_chat_id, story_id, is_pinned);
send_request(td_api::make_object<td_api::toggleStoryIsPinned>(story_id, is_pinned)); send_request(td_api::make_object<td_api::toggleStoryIsPinned>(story_sender_chat_id, story_id, is_pinned));
} else if (op == "ds") { } else if (op == "ds") {
ChatId story_sender_chat_id;
StoryId story_id; StoryId story_id;
get_args(args, story_id); get_args(args, story_sender_chat_id, story_id);
send_request(td_api::make_object<td_api::deleteStory>(story_id)); send_request(td_api::make_object<td_api::deleteStory>(story_sender_chat_id, story_id));
} else if (op == "las" || op == "lasa" || op == "lase") { } else if (op == "las" || op == "lasa" || op == "lase") {
send_request(td_api::make_object<td_api::loadActiveStories>(as_story_list(op))); send_request(td_api::make_object<td_api::loadActiveStories>(as_story_list(op)));
} else if (op == "scasl" || op == "scasla" || op == "scasle") { } else if (op == "scasl" || op == "scasla" || op == "scasle") {
@ -4198,11 +4211,12 @@ class CliClient final : public Actor {
string limit; string limit;
get_args(args, chat_id, from_story_id, limit); get_args(args, chat_id, from_story_id, limit);
send_request(td_api::make_object<td_api::getChatPinnedStories>(chat_id, from_story_id, as_limit(limit))); send_request(td_api::make_object<td_api::getChatPinnedStories>(chat_id, from_story_id, as_limit(limit)));
} else if (op == "gast") { } else if (op == "gcast") {
ChatId chat_id;
StoryId from_story_id; StoryId from_story_id;
string limit; string limit;
get_args(args, from_story_id, limit); get_args(args, chat_id, from_story_id, limit);
send_request(td_api::make_object<td_api::getArchivedStories>(from_story_id, as_limit(limit))); send_request(td_api::make_object<td_api::getChatArchivedStories>(chat_id, from_story_id, as_limit(limit)));
} else if (op == "gsnse") { } else if (op == "gsnse") {
send_request(td_api::make_object<td_api::getStoryNotificationSettingsExceptions>()); send_request(td_api::make_object<td_api::getStoryNotificationSettingsExceptions>());
} else if (op == "gcas") { } else if (op == "gcas") {