diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl
index a16e7d51a..9faad2bb7 100644
--- a/Telegram/Resources/scheme.tl
+++ b/Telegram/Resources/scheme.tl
@@ -1266,6 +1266,7 @@ channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet =
 channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
 channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
 channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
+channels.getLeftChannels#90920196 = messages.Chats;
 
 bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
 bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp
index cf55e70fc..f957b69be 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.cpp
+++ b/Telegram/SourceFiles/export/data/export_data_types.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "export/data/export_data_types.h"
 
+#include "export/export_settings.h"
 #include "core/mime_type.h"
 
 #include <QtCore/QDateTime>
@@ -891,6 +892,21 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) {
 	return result;
 }
 
+DialogInfo::Type DialogTypeFromChat(const Chat &chat) {
+	using Type = DialogInfo::Type;
+	return chat.username.isEmpty()
+		? (chat.broadcast
+			? Type::PrivateChannel
+			: Type::PrivateGroup)
+		: (chat.broadcast
+			? Type::PublicChannel
+			: Type::PublicGroup);
+}
+
+DialogInfo::Type DialogTypeFromUser(const User &user) {
+	return user.isBot ? DialogInfo::Type::Bot : DialogInfo::Type::Personal;
+}
+
 DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
 	auto result = DialogsInfo();
 	const auto folder = QString();
@@ -905,22 +921,14 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
 			const auto &fields = dialog.c_dialog();
 
 			auto info = DialogInfo();
-			const auto peerId = ParsePeerId(fields.vpeer);
-			const auto peerIt = peers.find(peerId);
+			info.peerId = ParsePeerId(fields.vpeer);
+			const auto peerIt = peers.find(info.peerId);
 			if (peerIt != end(peers)) {
 				using Type = DialogInfo::Type;
 				const auto &peer = peerIt->second;
 				info.type = peer.user()
-					? (peer.user()->isBot
-						? Type::Bot
-						: Type::Personal)
-					: (peer.chat()->broadcast
-						? (peer.chat()->username.isEmpty()
-							? Type::PrivateChannel
-							: Type::PublicChannel)
-						: (peer.chat()->username.isEmpty()
-							? Type::PrivateGroup
-							: Type::PublicGroup));
+					? DialogTypeFromUser(*peer.user())
+					: DialogTypeFromChat(*peer.chat());
 				info.name = peer.name();
 				info.input = peer.input();
 			}
@@ -936,6 +944,57 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
 	return result;
 }
 
+void InsertLeftDialog(
+		DialogsInfo &info,
+		const Chat &chat,
+		Message &&message) {
+	const auto projection = [](const DialogInfo &dialog) {
+		return std::make_tuple(
+			dialog.topMessageDate,
+			dialog.topMessageId,
+			BarePeerId(dialog.peerId));
+	};
+	const auto i = ranges::lower_bound(
+		info.list,
+		std::make_tuple(message.date, message.id, chat.id),
+		ranges::ordered_less{},
+		projection);
+
+	auto insert = DialogInfo();
+	insert.input = chat.input;
+	insert.name = chat.title;
+	insert.peerId = ChatPeerId(chat.id);
+	insert.topMessageDate = message.date;
+	insert.topMessageId = message.id;
+	insert.type = DialogTypeFromChat(chat);
+	info.list.insert(i, std::move(insert));
+}
+
+void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
+	auto &list = info.list;
+	const auto digits = Data::NumberToString(list.size() - 1).size();
+	auto index = 0;
+	for (auto &dialog : list) {
+		const auto number = Data::NumberToString(++index, digits, '0');
+		dialog.relativePath = "Chats/chat_" + number + '/';
+
+		using DialogType = DialogInfo::Type;
+		using Type = Settings::Type;
+		const auto setting = [&] {
+			switch (dialog.type) {
+			case DialogType::Personal: return Type::PersonalChats;
+			case DialogType::Bot: return Type::BotChats;
+			case DialogType::PrivateGroup: return Type::PrivateGroups;
+			case DialogType::PrivateChannel: return Type::PrivateChannels;
+			case DialogType::PublicGroup: return Type::PublicGroups;
+			case DialogType::PublicChannel: return Type::PublicChannels;
+			}
+			Unexpected("Type in ApiWrap::onlyMyMessages.");
+		}();
+		dialog.onlyMyMessages = ((settings.fullChats & setting) != setting);
+	}
+}
+
 MessagesSlice ParseMessagesSlice(
 		const MTPVector<MTPMessage> &data,
 		const MTPVector<MTPUser> &users,
diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h
index 5fa7dc8c5..f005a5ed2 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.h
+++ b/Telegram/SourceFiles/export/data/export_data_types.h
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include <vector>
 
 namespace Export {
+struct Settings;
 namespace Data {
 
 using TimeId = int32;
@@ -420,7 +421,10 @@ struct DialogInfo {
 	MTPInputPeer input = MTP_inputPeerEmpty();
 	int32 topMessageId = 0;
 	TimeId topMessageDate = 0;
+	PeerId peerId = 0;
 
+	// Filled after the whole dialogs list is accumulated.
+	bool onlyMyMessages = false;
 	QString relativePath;
 
 };
@@ -430,6 +434,11 @@ struct DialogsInfo {
 };
 
 DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data);
+void InsertLeftDialog(
+	DialogsInfo &info,
+	const Chat &chat,
+	Message &&message);
+void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings);
 
 struct MessagesSlice {
 	std::vector<Message> list;
diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp
index e95abe60c..c80df6eb0 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.cpp
+++ b/Telegram/SourceFiles/export/export_api_wrap.cpp
@@ -114,6 +114,7 @@ struct ApiWrap::FileProcess {
 
 struct ApiWrap::DialogsProcess {
 	Data::DialogsInfo info;
+	std::map<int32, Data::Chat> left;
 
 	FnMut<void(const Data::DialogsInfo&)> start;
 	Fn<void(const Data::DialogInfo&)> startOne;
@@ -350,8 +351,9 @@ void ApiWrap::loadNextUserpic() {
 		? base::none
 		: base::make_optional(list.back().id);
 
-	_userpicsProcess->handleSlice(*base::take(_userpicsProcess->slice));
-
+	if (!list.empty()) {
+		_userpicsProcess->handleSlice(*base::take(_userpicsProcess->slice));
+	}
 	if (_userpicsProcess->lastSlice) {
 		finishUserpics();
 		return;
@@ -431,19 +433,16 @@ void ApiWrap::requestDialogsSlice() {
 		_dialogsProcess->offsetPeer,
 		MTP_int(kChatsSliceLimit)
 	)).done([=](const MTPmessages_Dialogs &result) mutable {
-		const auto finished = [&] {
-			switch (result.type()) {
-			case mtpc_messages_dialogs: return true;
-			case mtpc_messages_dialogsSlice: {
-				const auto &data = result.c_messages_dialogsSlice();
-				return data.vdialogs.v.isEmpty();
-			} break;
-			default: Unexpected("Type in ApiWrap::requestChatsSlice.");
-			}
-		}();
+		const auto finished = result.match(
+		[](const MTPDmessages_dialogs &data) {
+			return true;
+		}, [](const MTPDmessages_dialogsSlice &data) {
+			return data.vdialogs.v.isEmpty();
+		});
+
 		auto info = Data::ParseDialogsInfo(result);
 		if (finished || info.list.empty()) {
-			finishDialogsList();
+			requestLeftChannels();
 		} else {
 			const auto &last = info.list.back();
 			_dialogsProcess->offsetId = last.topMessageId;
@@ -492,28 +491,83 @@ void ApiWrap::appendDialogsSlice(Data::DialogsInfo &&info) {
 	}
 }
 
+void ApiWrap::requestLeftChannels() {
+	Expects(_dialogsProcess != nullptr);
+
+	mainRequest(MTPchannels_GetLeftChannels(
+	)).done([=](const MTPmessages_Chats &result) mutable {
+		Expects(_dialogsProcess != nullptr);
+
+		const auto finished = result.match(
+		[](const MTPDmessages_chats &data) {
+			return true;
+		}, [](const MTPDmessages_chatsSlice &data) {
+			return data.vchats.v.isEmpty();
+		});
+
+		_dialogsProcess->left = Data::ParseChatsList(*result.match(
+		[](const auto &data) {
+			return &data.vchats;
+		}));
+		requestLeftDialog();
+	}).send();
+}
+
+void ApiWrap::requestLeftDialog() {
+	Expects(_dialogsProcess != nullptr);
+
+	auto &left = _dialogsProcess->left;
+	if (true || left.empty()) { // #TODO export
+		finishDialogsList();
+		return;
+	}
+	const auto key = std::move(*left.begin());
+	left.erase(key.first);
+
+	mainRequest(MTPmessages_Search(
+		MTP_flags(MTPmessages_Search::Flag::f_from_id),
+		key.second.input,
+		MTP_string(""), // query
+		_user,
+		MTP_inputMessagesFilterEmpty(),
+		MTP_int(0), // min_date
+		MTP_int(0), // max_date
+		MTP_int(0), // offset_id
+		MTP_int(0), // add_offset
+		MTP_int(1), // limit
+		MTP_int(0), // max_id
+		MTP_int(0), // min_id
+		MTP_int(0) // hash
+	)).done([=](const MTPmessages_Messages &result) {
+		Expects(_dialogsProcess != nullptr);
+
+		result.match([=](const MTPDmessages_messagesNotModified &data) {
+			error("Unexpected messagesNotModified received.");
+		}, [=](const auto &data) {
+			auto messages = Data::ParseMessagesList(
+				data.vmessages,
+				QString());
+			if (!messages.empty() && messages.begin()->second.date > 0) {
+				Data::InsertLeftDialog(
+					_dialogsProcess->info,
+					key.second,
+					std::move(messages.begin()->second));
+			}
+			requestLeftDialog();
+		});
+	}).send();
+}
+
 void ApiWrap::finishDialogsList() {
 	Expects(_dialogsProcess != nullptr);
 
 	ranges::reverse(_dialogsProcess->info.list);
-	fillDialogsPaths();
+	Data::FinalizeDialogsInfo(_dialogsProcess->info, *_settings);
 
 	_dialogsProcess->start(_dialogsProcess->info);
 	requestNextDialog();
 }
 
-void ApiWrap::fillDialogsPaths() {
-	Expects(_dialogsProcess != nullptr);
-
-	auto &list = _dialogsProcess->info.list;
-	const auto digits = Data::NumberToString(list.size() - 1).size();
-	auto index = 0;
-	for (auto &dialog : list) {
-		const auto number = Data::NumberToString(++index, digits, '0');
-		dialog.relativePath = "Chats/chat_" + number + '/';
-	}
-}
-
 void ApiWrap::requestNextDialog() {
 	Expects(_dialogsProcess != nullptr);
 	Expects(_dialogsProcess->single == nullptr);
@@ -534,6 +588,15 @@ void ApiWrap::requestMessagesSlice() {
 	Expects(_dialogsProcess->single != nullptr);
 
 	const auto process = _dialogsProcess->single.get();
+
+	// #TODO export
+	if (process->info.input.match([](const MTPDinputPeerUser &value) {
+		return !value.vaccess_hash.v;
+	}, [](const auto &data) { return false; })) {
+		finishMessages();
+		return;
+	}
+
 	auto handleResult = [=](const MTPmessages_Messages &result) mutable {
 		Expects(_dialogsProcess != nullptr);
 		Expects(_dialogsProcess->single != nullptr);
@@ -552,7 +615,7 @@ void ApiWrap::requestMessagesSlice() {
 				process->info.relativePath));
 		});
 	};
-	if (onlyMyMessages()) {
+	if (process->info.onlyMyMessages) {
 		mainRequest(MTPmessages_Search(
 			MTP_flags(MTPmessages_Search::Flag::f_from_id),
 			process->info.input,
@@ -582,26 +645,6 @@ void ApiWrap::requestMessagesSlice() {
 	}
 }
 
-bool ApiWrap::onlyMyMessages() const {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single != nullptr);
-
-	const auto process = _dialogsProcess->single.get();
-	using Type = Data::DialogInfo::Type;
-	const auto setting = [&] {
-		switch (process->info.type) {
-		case Type::Personal: return Settings::Type::PersonalChats;
-		case Type::Bot: return Settings::Type::BotChats;
-		case Type::PrivateGroup: return Settings::Type::PrivateGroups;
-		case Type::PrivateChannel: return Settings::Type::PrivateChannels;
-		case Type::PublicGroup: return Settings::Type::PublicGroups;
-		case Type::PublicChannel: return Settings::Type::PublicChannels;
-		}
-		Unexpected("Type in ApiWrap::onlyMyMessages.");
-	}();
-	return (_settings->fullChats & setting) != setting;
-}
-
 void ApiWrap::loadMessagesFiles(Data::MessagesSlice &&slice) {
 	Expects(_dialogsProcess != nullptr);
 	Expects(_dialogsProcess->single != nullptr);
@@ -637,12 +680,20 @@ void ApiWrap::loadNextMessageFile() {
 			return;
 		}
 	}
+	finishMessagesSlice();
+}
 
-	if (!list.empty()) {
-		process->offsetId = list.back().id + 1;
+void ApiWrap::finishMessagesSlice() {
+	Expects(_dialogsProcess != nullptr);
+	Expects(_dialogsProcess->single != nullptr);
+	Expects(_dialogsProcess->single->slice.has_value());
+
+	const auto process = _dialogsProcess->single.get();
+	auto slice = *base::take(process->slice);
+	if (!slice.list.empty()) {
+		process->offsetId = slice.list.back().id + 1;
+		_dialogsProcess->sliceOne(std::move(slice));
 	}
-	_dialogsProcess->sliceOne(*base::take(process->slice));
-
 	if (process->lastSlice) {
 		finishMessages();
 	} else {
diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h
index 367e8c4c3..9b9a4b0f2 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.h
+++ b/Telegram/SourceFiles/export/export_api_wrap.h
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace Export {
 namespace Data {
 struct File;
+struct Chat;
 struct FileLocation;
 struct PersonalInfo;
 struct UserpicsInfo;
@@ -72,16 +73,16 @@ private:
 
 	void requestDialogsSlice();
 	void appendDialogsSlice(Data::DialogsInfo &&info);
+	void requestLeftChannels();
+	void requestLeftDialog();
 	void finishDialogsList();
-	void fillDialogsPaths();
 
 	void requestNextDialog();
 	void requestMessagesSlice();
-	bool onlyMyMessages() const;
 	void loadMessagesFiles(Data::MessagesSlice &&slice);
 	void loadNextMessageFile();
-
 	void loadMessageFileDone(const QString &relativePath);
+	void finishMessagesSlice();
 	void finishMessages();
 	void finishDialogs();
 
diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp
index 7d02f988b..23d2db12c 100644
--- a/Telegram/SourceFiles/export/output/export_output_text.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_text.cpp
@@ -30,12 +30,17 @@ void SerializeMultiline(
 	auto offset = 0;
 	do {
 		appendTo.append("> ");
+		const auto win = (newline > 0 && *(data + newline - 1) == '\r');
+		if (win) --newline;
 		appendTo.append(data + offset, newline - offset).append(kLineBreak);
+		if (win) ++newline;
 		offset = newline + 1;
 		newline = value.indexOf('\n', offset);
 	} while (newline > 0);
-	appendTo.append("> ");
-	appendTo.append(data + offset).append(kLineBreak);
+	if (const auto size = value.size(); size > offset) {
+		appendTo.append("> ");
+		appendTo.append(data + offset, size - offset).append(kLineBreak);
+	}
 }
 
 QByteArray JoinList(
@@ -467,6 +472,9 @@ bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
 }
 
 bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
+	Expects(_result != nullptr);
+	Expects(!data.list.empty());
+
 	auto lines = QByteArray();
 	for (const auto &userpic : data.list) {
 		if (!userpic.date) {
@@ -485,12 +493,16 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
 }
 
 bool TextWriter::writeUserpicsEnd() {
+	Expects(_result != nullptr);
+
 	return (_userpicsCount > 0)
 		? _result->writeBlock(kLineBreak) == File::Result::Success
 		: true;
 }
 
 bool TextWriter::writeContactsList(const Data::ContactsList &data) {
+	Expects(_result != nullptr);
+
 	if (data.list.empty()) {
 		return true;
 	}
@@ -529,6 +541,8 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
 }
 
 bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
+	Expects(_result != nullptr);
+
 	if (data.list.empty()) {
 		return true;
 	}
@@ -568,6 +582,8 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
 }
 
 bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
+	Expects(_result != nullptr);
+
 	if (data.list.empty()) {
 		return true;
 	}
@@ -635,12 +651,16 @@ bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
 	const auto digits = Data::NumberToString(_dialogsCount - 1).size();
 	const auto number = Data::NumberToString(++_dialogIndex, digits, '0');
 	_dialog = fileWithRelativePath(data.relativePath + "messages.txt");
+	_dialogEmpty = true;
+	_dialogOnlyMy = data.onlyMyMessages;
 	return true;
 }
 
 bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
 	Expects(_dialog != nullptr);
+	Expects(!data.list.empty());
 
+	_dialogEmpty = false;
 	auto list = std::vector<QByteArray>();
 	list.reserve(data.list.size());
 	auto index = 0;
@@ -659,6 +679,11 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
 bool TextWriter::writeDialogEnd() {
 	Expects(_dialog != nullptr);
 
+	if (_dialogEmpty) {
+		_dialog->writeBlock(_dialogOnlyMy
+			? "No outgoing messages in this chat."
+			: "No messages in this chat.");
+	}
 	_dialog = nullptr;
 	return true;
 }
diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h
index 714684386..d6f20ccdf 100644
--- a/Telegram/SourceFiles/export/output/export_output_text.h
+++ b/Telegram/SourceFiles/export/output/export_output_text.h
@@ -50,6 +50,8 @@ private:
 
 	int _dialogsCount = 0;
 	int _dialogIndex = 0;
+	bool _dialogOnlyMy = false;
+	bool _dialogEmpty = true;
 	std::unique_ptr<File> _dialog;
 
 };