diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 115a19773..61cd43f59 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2134,6 +2134,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_admin_log_deleted_message" = "{from} deleted message:";
 "lng_admin_log_participant_joined" = "{from} joined the group";
 "lng_admin_log_participant_joined_channel" = "{from} joined the channel";
+"lng_admin_log_participant_joined_by_link" = "{from} joined the group via {link}";
+"lng_admin_log_participant_joined_by_link_channel" = "{from} joined the channel via {link}";
+"lng_admin_log_revoke_invite_link" = "{from} revoked invite link {link}";
+"lng_admin_log_delete_invite_link" = "{from} deleted invite link {link}";
 "lng_admin_log_participant_left" = "{from} left the group";
 "lng_admin_log_participant_left_channel" = "{from} left the channel";
 "lng_admin_log_stopped_poll" = "{from} stopped poll:";
@@ -2160,7 +2164,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_admin_log_unmuted_participant" = "{from} unmuted {user} in a voice chat";
 "lng_admin_log_allowed_unmute_self" = "{from} allowed new voice chat members to speak";
 "lng_admin_log_disallowed_unmute_self" = "{from} started muting new voice chat members";
+"lng_admin_log_participant_volume" = "{from} changed voice chat volume for {user} to {percent}";
 "lng_admin_log_user_with_username" = "{name} ({mention})";
+"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
+"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
+"lng_admin_log_messages_ttl_removed" = "{from} disabled messages auto-deletion after {duration}";
+"lng_admin_log_edited_invite_link" = "edited invite link {link}";
+"lng_admin_log_invite_link_expire_date" = "Expire date: {previous} -> {limit}";
+"lng_admin_log_invite_link_usage_limit" = "Usage limit: {previous} -> {limit}";
 "lng_admin_log_restricted_forever" = "indefinitely";
 "lng_admin_log_restricted_until" = "until {date}";
 "lng_admin_log_banned_view_messages" = "Read messages";
diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp
index 2b945fd55..5bc25514b 100644
--- a/Telegram/SourceFiles/api/api_invite_links.cpp
+++ b/Telegram/SourceFiles/api/api_invite_links.cpp
@@ -238,8 +238,8 @@ void InviteLinks::performEdit(
 	using Flag = MTPmessages_EditExportedChatInvite::Flag;
 	_api->request(MTPmessages_EditExportedChatInvite(
 		MTP_flags((revoke ? Flag::f_revoked : Flag(0))
-			| ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0))
-			| ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))),
+			| (!revoke ? Flag::f_expire_date : Flag(0))
+			| (!revoke ? Flag::f_usage_limit : Flag(0))),
 		peer->input,
 		MTP_string(link),
 		MTP_int(expireDate),
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index 9d47fa80d..5a3fa6701 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/language_box.h"
 #include "passport/passport_form_controller.h"
 #include "window/window_session_controller.h"
+#include "ui/toast/toast.h"
 #include "data/data_session.h"
 #include "data/data_document.h"
 #include "data/data_cloud_themes.h"
@@ -40,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "apiwrap.h"
 #include "app.h"
 
+#include <QtGui/QGuiApplication>
+
 namespace Core {
 namespace {
 
@@ -433,6 +436,23 @@ bool OpenMediaTimestamp(
 	return false;
 }
 
+bool ShowInviteLink(
+		Window::SessionController *controller,
+		const Match &match,
+		const QVariant &context) {
+	if (!controller) {
+		return false;
+	}
+	const auto base64link = match->captured(1).toLatin1();
+	const auto link = QString::fromUtf8(QByteArray::fromBase64(base64link));
+	if (link.isEmpty()) {
+		return false;
+	}
+	QGuiApplication::clipboard()->setText(link);
+	Ui::Toast::Show(tr::lng_group_invite_copied(tr::now));
+	return true;
+}
+
 } // namespace
 
 const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
@@ -507,6 +527,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
 			qsl("^media_timestamp/?\\?base=([a-zA-Z0-9\\.\\_\\-]+)&t=(\\d+)(&|$)"),
 			OpenMediaTimestamp
 		},
+		{
+			qsl("^show_invite_link/?\\?link=([a-zA-Z0-9_\\+\\/\\=\\-]+)(&|$)"),
+			ShowInviteLink
+		},
 	};
 	return Result;
 }
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
index 6c6f57517..02b820bdb 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "lang/lang_keys.h"
 #include "ui/text/text_utilities.h"
+#include "ui/basic_click_handlers.h"
 #include "boxes/sticker_set_box.h"
 #include "base/unixtime.h"
 #include "core/application.h"
@@ -261,6 +262,86 @@ TextWithEntities GenerateBannedChangeText(
 	return result;
 }
 
+QString ExtractInviteLink(const MTPExportedChatInvite &data) {
+	return data.match([&](const MTPDchatInviteExported &data) {
+		return qs(data.vlink());
+	});
+}
+
+QString InternalInviteLinkUrl(const MTPExportedChatInvite &data) {
+	const auto base64 = ExtractInviteLink(data).toUtf8().toBase64();
+	return "internal:show_invite_link/?link=" + QString::fromLatin1(base64);
+}
+
+QString GenerateInviteLinkText(const MTPExportedChatInvite &data) {
+	return ExtractInviteLink(data).replace(
+		qstr("https://"),
+		QString()
+	).replace(
+		qstr("t.me/+"),
+		QString()
+	).replace(
+		qstr("t.me/joinchat/"),
+		QString()
+	);
+}
+
+QString GenerateInviteLinkLink(const MTPExportedChatInvite &data) {
+	const auto text = GenerateInviteLinkText(data);
+	return text.endsWith("...")
+		? text
+		: textcmdLink(InternalInviteLinkUrl(data), text);
+}
+
+TextWithEntities GenerateInviteLinkChangeText(
+		const MTPExportedChatInvite &newLink,
+		const MTPExportedChatInvite &prevLink) {
+	auto link = TextWithEntities{ GenerateInviteLinkText(newLink) };
+	if (!link.text.endsWith("...")) {
+		link.entities.push_back({
+			EntityType::CustomUrl,
+			0,
+			link.text.size(),
+			InternalInviteLinkUrl(newLink) });
+	}
+	auto result = tr::lng_admin_log_edited_invite_link(tr::now, lt_link, link, Ui::Text::WithEntities);
+	result.text.append('\n');
+
+	const auto expireDate = [](const MTPExportedChatInvite &link) {
+		return link.match([](const MTPDchatInviteExported &data) {
+			return data.vexpire_date().value_or_empty();
+		});
+	};
+	const auto usageLimit = [](const MTPExportedChatInvite &link) {
+		return link.match([](const MTPDchatInviteExported &data) {
+			return data.vusage_limit().value_or_empty();
+		});
+	};
+	const auto wrapDate = [](TimeId date) {
+		return date
+			? langDateTime(base::unixtime::parse(date))
+			: tr::lng_group_invite_expire_never(tr::now);
+	};
+	const auto wrapUsage = [](int count) {
+		return count
+			? QString::number(count)
+			: tr::lng_group_invite_usage_any(tr::now);
+	};
+	const auto wasExpireDate = expireDate(prevLink);
+	const auto nowExpireDate = expireDate(newLink);
+	const auto wasUsageLimit = usageLimit(prevLink);
+	const auto nowUsageLimit = usageLimit(newLink);
+	if (wasExpireDate != nowExpireDate) {
+		result.text.append('\n').append(tr::lng_admin_log_invite_link_expire_date(tr::now, lt_previous, wrapDate(wasExpireDate), lt_limit, wrapDate(nowExpireDate)));
+	}
+	if (wasUsageLimit != nowUsageLimit) {
+		result.text.append('\n').append(tr::lng_admin_log_invite_link_usage_limit(tr::now, lt_previous, wrapUsage(wasUsageLimit), lt_limit, wrapUsage(nowUsageLimit)));
+	}
+
+	result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size()));
+	return result;
+};
+
 auto GenerateUserString(
 		not_null<Main::Session*> session,
 		MTPint userId) {
@@ -863,52 +944,49 @@ void GenerateItems(
 		addSimpleServiceMessage(text);
 	};
 
-	auto createParticipantMute = [&](const MTPDchannelAdminLogEventActionParticipantMute &data) {
-		data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
-			const auto user = history->owner().user(data.vuser_id().v);
-			const auto userLink = user->createOpenLink();
-			const auto userLinkText = textcmdLink(2, user->name);
-			auto text = tr::lng_admin_log_muted_participant(
-				tr::now,
-				lt_from,
-				fromLinkText,
-				lt_user,
-				userLinkText);
-			auto message = HistoryService::PreparedText{ text };
-			message.links.push_back(fromLink);
-			message.links.push_back(userLink);
-			addPart(history->makeServiceMessage(
-				history->nextNonHistoryEntryId(),
-				MTPDmessage_ClientFlag::f_admin_log_entry,
-				date,
-				message,
-				MTPDmessage::Flags(0),
-				peerToUser(from->id)));
+	auto groupCallParticipantUser = [&](const MTPGroupCallParticipant &data) {
+		return data.match([&](const MTPDgroupCallParticipant &data) {
+			return history->owner().user(data.vuser_id().v);
 		});
 	};
 
+	auto addServiceMessageWithLink = [&](const QString &text, const ClickHandlerPtr &link) {
+		auto message = HistoryService::PreparedText{ text };
+		message.links.push_back(fromLink);
+		message.links.push_back(link);
+		addPart(history->makeServiceMessage(
+			history->nextNonHistoryEntryId(),
+			MTPDmessage_ClientFlag::f_admin_log_entry,
+			date,
+			message,
+			MTPDmessage::Flags(0),
+			peerToUser(from->id)));
+	};
+
+	auto createParticipantMute = [&](const MTPDchannelAdminLogEventActionParticipantMute &data) {
+		const auto user = groupCallParticipantUser(data.vparticipant());
+		const auto userLink = user->createOpenLink();
+		const auto userLinkText = textcmdLink(2, user->name);
+		auto text = tr::lng_admin_log_muted_participant(
+			tr::now,
+			lt_from,
+			fromLinkText,
+			lt_user,
+			userLinkText);
+		addServiceMessageWithLink(text, userLink);
+	};
+
 	auto createParticipantUnmute = [&](const MTPDchannelAdminLogEventActionParticipantUnmute &data) {
-		data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
-			const auto user = history->owner().user(data.vuser_id().v);
-			const auto userLink = user->createOpenLink();
-			const auto userLinkText = textcmdLink(2, user->name);
-			auto text = tr::lng_admin_log_unmuted_participant(
-				tr::now,
-				lt_from,
-				fromLinkText,
-				lt_user,
-				userLinkText);
-			auto message = HistoryService::PreparedText{ text };
-			message.links.push_back(fromLink);
-			message.links.push_back(userLink);
-			addPart(history->makeServiceMessage(
-				history->nextNonHistoryEntryId(),
-				MTPDmessage_ClientFlag::f_admin_log_entry,
-				date,
-				message,
-				MTPDmessage::Flags(0),
-				peerToUser(from->id)));
-		});
+		const auto user = groupCallParticipantUser(data.vparticipant());
+		const auto userLink = user->createOpenLink();
+		const auto userLinkText = textcmdLink(2, user->name);
+		auto text = tr::lng_admin_log_unmuted_participant(
+			tr::now,
+			lt_from,
+			fromLinkText,
+			lt_user,
+			userLinkText);
+		addServiceMessageWithLink(text, userLink);
 	};
 
 	auto createToggleGroupCallSetting = [&](const MTPDchannelAdminLogEventActionToggleGroupCallSetting &data) {
@@ -918,6 +996,114 @@ void GenerateItems(
 		addSimpleServiceMessage(text);
 	};
 
+	auto addInviteLinkServiceMessage = [&](const QString &text, const MTPExportedChatInvite &data) {
+		auto message = HistoryService::PreparedText{ text };
+		message.links.push_back(fromLink);
+		if (!ExtractInviteLink(data).endsWith("...")) {
+			message.links.push_back(std::make_shared<UrlClickHandler>(InternalInviteLinkUrl(data)));
+		}
+		addPart(history->makeServiceMessage(
+			history->nextNonHistoryEntryId(),
+			MTPDmessage_ClientFlag::f_admin_log_entry,
+			date,
+			message,
+			MTPDmessage::Flags(0),
+			peerToUser(from->id),
+			nullptr));
+	};
+
+	auto createParticipantJoinByInvite = [&](const MTPDchannelAdminLogEventActionParticipantJoinByInvite &data) {
+		auto text = (channel->isMegagroup()
+			? tr::lng_admin_log_participant_joined_by_link
+			: tr::lng_admin_log_participant_joined_by_link_channel);
+		addInviteLinkServiceMessage(
+			text(
+				tr::now,
+				lt_from,
+				fromLinkText,
+				lt_link,
+				GenerateInviteLinkLink(data.vinvite())),
+			data.vinvite());
+	};
+
+	auto createExportedInviteDelete = [&](const MTPDchannelAdminLogEventActionExportedInviteDelete &data) {
+		addInviteLinkServiceMessage(
+			tr::lng_admin_log_delete_invite_link(
+				tr::now,
+				lt_from,
+				fromLinkText,
+				lt_link,
+				GenerateInviteLinkLink(data.vinvite())),
+			data.vinvite());
+	};
+
+	auto createExportedInviteRevoke = [&](const MTPDchannelAdminLogEventActionExportedInviteRevoke &data) {
+		addInviteLinkServiceMessage(
+			tr::lng_admin_log_revoke_invite_link(
+				tr::now,
+				lt_from,
+				fromLinkText,
+				lt_link,
+				GenerateInviteLinkLink(data.vinvite())),
+			data.vinvite());
+	};
+
+	auto createExportedInviteEdit = [&](const MTPDchannelAdminLogEventActionExportedInviteEdit &data) {
+		auto bodyFlags = Flag::f_entities | Flag::f_from_id;
+		auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry;
+		auto bodyReplyTo = 0;
+		auto bodyViaBotId = 0;
+		auto bodyText = GenerateInviteLinkChangeText(data.vnew_invite(), data.vprev_invite());
+		addPart(history->makeMessage(
+			history->nextNonHistoryEntryId(),
+			bodyFlags,
+			bodyClientFlags,
+			bodyReplyTo,
+			bodyViaBotId,
+			date,
+			peerToUser(from->id),
+			QString(),
+			bodyText));
+	};
+
+	auto createParticipantVolume = [&](const MTPDchannelAdminLogEventActionParticipantVolume &data) {
+		const auto user = groupCallParticipantUser(data.vparticipant());
+		const auto userLink = user->createOpenLink();
+		const auto userLinkText = textcmdLink(2, user->name);
+		const auto volume = data.vparticipant().match([&](
+				const MTPDgroupCallParticipant &data) {
+			return data.vvolume().value_or(10000);
+		});
+		const auto volumeText = QString::number(volume / 100) + '%';
+		auto text = tr::lng_admin_log_participant_volume(
+			tr::now,
+			lt_from,
+			fromLinkText,
+			lt_user,
+			userLinkText,
+			lt_percent,
+			volumeText);
+		addServiceMessageWithLink(text, userLink);
+	};
+
+	auto createChangeHistoryTTL = [&](const MTPDchannelAdminLogEventActionChangeHistoryTTL &data) {
+		const auto was = data.vprev_value().v;
+		const auto now = data.vnew_value().v;
+		const auto wrap = [](int duration) {
+			return (duration == 5)
+				? u"5 seconds"_q
+				: (duration < 3 * 86400)
+				? tr::lng_manage_messages_ttl_after1(tr::now)
+				: tr::lng_manage_messages_ttl_after2(tr::now);
+		};
+		auto text = !was
+			? tr::lng_admin_log_messages_ttl_set(tr::now, lt_from, fromLinkText, lt_duration, wrap(now))
+			: !now
+			? tr::lng_admin_log_messages_ttl_removed(tr::now, lt_from, fromLinkText, lt_duration, wrap(was))
+			: tr::lng_admin_log_messages_ttl_changed(tr::now, lt_from, fromLinkText, lt_previous, wrap(was), lt_duration, wrap(now));
+		addSimpleServiceMessage(text);
+	};
+
 	action.match([&](const MTPDchannelAdminLogEventActionChangeTitle &data) {
 		createChangeTitle(data);
 	}, [&](const MTPDchannelAdminLogEventActionChangeAbout &data) {
@@ -971,14 +1157,17 @@ void GenerateItems(
 	}, [&](const MTPDchannelAdminLogEventActionToggleGroupCallSetting &data) {
 		createToggleGroupCallSetting(data);
 	}, [&](const MTPDchannelAdminLogEventActionParticipantJoinByInvite &data) {
+		createParticipantJoinByInvite(data);
 	}, [&](const MTPDchannelAdminLogEventActionExportedInviteDelete &data) {
+		createExportedInviteDelete(data);
 	}, [&](const MTPDchannelAdminLogEventActionExportedInviteRevoke &data) {
+		createExportedInviteRevoke(data);
 	}, [&](const MTPDchannelAdminLogEventActionExportedInviteEdit &data) {
-		// #TODO links
+		createExportedInviteEdit(data);
 	}, [&](const MTPDchannelAdminLogEventActionParticipantVolume &data) {
-		// #TODO calls
+		createParticipantVolume(data);
 	}, [&](const MTPDchannelAdminLogEventActionChangeHistoryTTL &data) {
-		// #TODO ttl
+		createChangeHistoryTTL(data);
 	});
 }