diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index f1948355d..0433bcd7b 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -286,8 +286,20 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
 		} else {
 			channel->photoId = 0;
 		}
+		if (channel->mgInfo) {
+			if (f.has_migrated_from_chat_id()) {
+				channel->mgInfo->migrateFrom = App::chat(peerFromChat(f.vmigrated_from_chat_id));
+				channel->mgInfo->migrateFrom->migrateTo = channel;
+			}
+		}
 		channel->about = qs(f.vabout);
-		channel->count = f.has_participants_count() ? f.vparticipants_count.v : 0;
+		int32 newCount = f.has_participants_count() ? f.vparticipants_count.v : 0;
+		if (newCount != channel->count) {
+			channel->count = newCount;
+			if (channel->isMegagroup() && !channel->mgInfo->lastParticipants.isEmpty()) {
+				requestLastParticipants(channel);
+			}
+		}
 		channel->adminsCount = f.has_admins_count() ? f.vadmins_count.v : 0;
 		channel->invitationUrl = (f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString();
 		if (History *h = App::historyLoaded(channel->id)) {
@@ -296,6 +308,9 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
 				h->inboxReadBefore = f.vread_inbox_max_id.v + 1;
 				h->asChannelHistory()->unreadCountAll = f.vunread_count.v;
 			}
+			if (channel->mgInfo && channel->mgInfo->migrateFrom) {
+				h->asChannelHistory()->removeJoinedMessage();
+			}
 		}
 		channel->fullUpdated();
 
@@ -385,6 +400,13 @@ void ApiWrap::requestPeers(const QList<PeerData*> &peers) {
 	if (!users.isEmpty()) MTP::send(MTPusers_GetUsers(MTP_vector<MTPInputUser>(users)), rpcDone(&ApiWrap::gotUsers));
 }
 
+void ApiWrap::requestLastParticipants(ChannelData *peer) {
+	if (!peer || !peer->isMegagroup() || _participantsRequests.contains(peer)) return;
+	mtpRequestId req = MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsRecent(), MTP_int(0), MTP_int(cMaxGroupCount())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer));
+	_participantsRequests.insert(peer, req);
+	MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsBots(), MTP_int(0), MTP_int(cMaxGroupCount())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer));
+}
+
 void ApiWrap::gotChat(PeerData *peer, const MTPmessages_Chats &result) {
 	_peerRequests.remove(peer);
 	
@@ -433,6 +455,64 @@ bool ApiWrap::gotPeerFailed(PeerData *peer, const RPCError &error) {
 	return true;
 }
 
+void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelParticipants &result, mtpRequestId req) {
+	bool bots = (_participantsRequests.value(peer) != req);
+
+	if (!bots) {
+		_participantsRequests.remove(peer);
+	}
+	if (!peer->mgInfo) return;
+
+	if (result.type() == mtpc_channels_channelParticipants) {
+		const MTPDchannels_channelParticipants &d(result.c_channels_channelParticipants());
+		const QVector<MTPChannelParticipant> &v(d.vparticipants.c_vector().v);
+		App::feedUsers(d.vusers);
+		int32 botStatus = peer->mgInfo->botStatus;
+		if (bots) {
+			peer->mgInfo->bots.clear();
+			botStatus = -1;
+		} else {
+			peer->mgInfo->lastParticipants.clear();
+			peer->mgInfo->lastAdmins.clear();
+		}
+		for (QVector<MTPChannelParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
+			int32 userId = 0;
+			bool admin = false;
+
+			switch (i->type()) {
+			case mtpc_channelParticipant: userId = i->c_channelParticipant().vuser_id.v; break;
+			case mtpc_channelParticipantSelf: userId = i->c_channelParticipantSelf().vuser_id.v; break;
+			case mtpc_channelParticipantModerator: userId = i->c_channelParticipantModerator().vuser_id.v; break;
+			case mtpc_channelParticipantEditor: userId = i->c_channelParticipantEditor().vuser_id.v; admin = true; break;
+			case mtpc_channelParticipantKicked: userId = i->c_channelParticipantKicked().vuser_id.v; break;
+			case mtpc_channelParticipantCreator: userId = i->c_channelParticipantCreator().vuser_id.v; admin = true; break;
+			}
+			UserData *u = App::user(userId);
+			if (bots) {
+				if (u->botInfo) {
+					peer->mgInfo->bots.insert(u, true);
+					botStatus = (botStatus > 0/* || i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
+				}
+			} else {
+				peer->mgInfo->lastParticipants.push_back(u);
+				if (admin) peer->mgInfo->lastAdmins.insert(u, true);
+			}
+		}
+		if (d.vcount.v > peer->count) {
+			peer->count = d.vcount.v;
+		} else if (v.count() > peer->count) {
+			peer->count = v.count();
+		}
+		if (App::main()) emit fullPeerUpdated(peer);
+	}
+}
+
+bool ApiWrap::lastParticipantsFail(ChannelData *peer, const RPCError &error) {
+	if (mtpIsFlood(error)) return false;
+	_participantsRequests.remove(peer);
+	return true;
+}
+
 void ApiWrap::requestSelfParticipant(ChannelData *channel) {
 	if (_selfParticipantRequests.contains(channel)) return;
 	_selfParticipantRequests.insert(channel, MTP::send(MTPchannels_GetParticipant(channel->inputChannel, MTP_inputUserSelf()), rpcDone(&ApiWrap::gotSelfParticipant, channel), rpcFail(&ApiWrap::gotSelfParticipantFail, channel), 0, 5));
@@ -487,6 +567,36 @@ bool ApiWrap::gotSelfParticipantFail(ChannelData *channel, const RPCError &error
 	return true;
 }
 
+void ApiWrap::kickParticipant(PeerData *peer, UserData *user) {
+	KickRequest req(peer, user);
+	if (_kickRequests.contains(req));
+	if (peer->isChannel()) {
+		_kickRequests.insert(req, MTP::send(MTPchannels_KickFromChannel(peer->asChannel()->inputChannel, user->inputUser, MTP_bool(true)), rpcDone(&ApiWrap::kickParticipantDone, req), rpcFail(&ApiWrap::kickParticipantFail, req)));
+	}
+}
+
+void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mtpRequestId req) {
+	_kickRequests.remove(kick);
+	if (kick.first->isMegagroup()) {
+		int32 i = kick.first->asChannel()->mgInfo->lastParticipants.indexOf(kick.second);
+		if (i >= 0) {
+			kick.first->asChannel()->mgInfo->lastParticipants.removeAt(i);
+			kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second);
+			kick.first->asChannel()->mgInfo->bots.remove(kick.second);
+		}
+		if (kick.first->asChannel()->count > 1) {
+			kick.first->asChannel()->count--;
+		}
+	}
+	emit fullPeerUpdated(kick.first);
+}
+
+bool ApiWrap::kickParticipantFail(KickRequest kick, const RPCError &error, mtpRequestId req) {
+	if (mtpIsFlood(error)) return false;
+	_kickRequests.remove(kick);
+	return true;
+}
+
 void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) {
 	if (!_stickerSetRequests.contains(setId)) {
 		_stickerSetRequests.insert(setId, qMakePair(access, 0));
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 4529d935f..1f2a6be95 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -36,11 +36,13 @@ public:
 	void requestFullPeer(PeerData *peer);
 	void requestPeer(PeerData *peer);
 	void requestPeers(const QList<PeerData*> &peers);
+	void requestLastParticipants(ChannelData *peer);
 
 	void processFullPeer(PeerData *peer, const MTPmessages_ChatFull &result);
 	void processFullPeer(PeerData *peer, const MTPUserFull &result);
 
 	void requestSelfParticipant(ChannelData *channel);
+	void kickParticipant(PeerData *peer, UserData *user);
 
 	void requestWebPageDelayed(WebPageData *page);
 	void clearWebPageRequest(WebPageData *page);
@@ -91,6 +93,16 @@ private:
 	bool gotPeerFailed(PeerData *peer, const RPCError &err);
 	PeerRequests _peerRequests;
 
+	void lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelParticipants &result, mtpRequestId req);
+	bool lastParticipantsFail(ChannelData *peer, const RPCError &error);
+	PeerRequests _participantsRequests;
+
+	typedef QPair<PeerData*, UserData*> KickRequest;
+	typedef QMap<KickRequest, mtpRequestId> KickRequests;
+	void kickParticipantDone(KickRequest kick, const MTPUpdates &updates, mtpRequestId req);
+	bool kickParticipantFail(KickRequest kick, const RPCError &error, mtpRequestId req);
+	KickRequests _kickRequests;
+
 	void gotSelfParticipant(ChannelData *channel, const MTPchannels_ChannelParticipant &result);
 	bool gotSelfParticipantFail(ChannelData *channel, const RPCError &error);
 	typedef QMap<ChannelData*, mtpRequestId> SelfParticipantRequests;
diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp
index 1337087df..def85ee2c 100644
--- a/Telegram/SourceFiles/boxes/addcontactbox.cpp
+++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp
@@ -593,7 +593,7 @@ void GroupInfoBox::onPhoto() {
 	if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) {
 		return;
 	}
-	PhotoCropBox *box = new PhotoCropBox(img, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0), false);
+	PhotoCropBox *box = new PhotoCropBox(img, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0));
 	connect(box, SIGNAL(ready(const QImage&)), this, SLOT(onPhotoReady(const QImage&)));
 	App::wnd()->replaceLayer(box);
 }
diff --git a/Telegram/SourceFiles/boxes/photocropbox.cpp b/Telegram/SourceFiles/boxes/photocropbox.cpp
index 509451471..e76410712 100644
--- a/Telegram/SourceFiles/boxes/photocropbox.cpp
+++ b/Telegram/SourceFiles/boxes/photocropbox.cpp
@@ -27,13 +27,26 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
 #include "photocropbox.h"
 #include "fileuploader.h"
 
-PhotoCropBox::PhotoCropBox(const QImage &img, const PeerId &peer, bool upload) : AbstractBox()
+PhotoCropBox::PhotoCropBox(const QImage &img, const PeerId &peer) : AbstractBox()
 , _downState(0)
 , _done(this, lang(lng_settings_save), st::defaultBoxButton)
 , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 , _img(img)
 , _peerId(peer) {
-	if (peerIsChat(_peerId)) {
+	init(img, 0);
+}
+
+PhotoCropBox::PhotoCropBox(const QImage &img, PeerData *peer) : AbstractBox()
+, _downState(0)
+, _done(this, lang(lng_settings_save), st::defaultBoxButton)
+, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
+, _img(img)
+, _peerId(peer->id) {
+	init(img, peer);
+}
+
+void PhotoCropBox::init(const QImage &img, PeerData *peer) {
+	if (peerIsChat(_peerId) || (peer && peer->isMegagroup())) {
 		_title = lang(lng_create_group_crop);
 	} else if (peerIsChannel(_peerId)) {
 		_title = lang(lng_create_channel_crop);
@@ -43,7 +56,7 @@ PhotoCropBox::PhotoCropBox(const QImage &img, const PeerId &peer, bool upload) :
 
 	connect(&_done, SIGNAL(clicked()), this, SLOT(onSend()));
 	connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
-	if (_peerId && upload) {
+	if (peerToBareInt(_peerId)) {
 		connect(this, SIGNAL(ready(const QImage&)), this, SLOT(onReady(const QImage&)));
 	}
 
diff --git a/Telegram/SourceFiles/boxes/photocropbox.h b/Telegram/SourceFiles/boxes/photocropbox.h
index c9262a284..347d24064 100644
--- a/Telegram/SourceFiles/boxes/photocropbox.h
+++ b/Telegram/SourceFiles/boxes/photocropbox.h
@@ -27,7 +27,8 @@ class PhotoCropBox : public AbstractBox {
 
 public:
 
-	PhotoCropBox(const QImage &img, const PeerId &peer, bool upload = true);
+	PhotoCropBox(const QImage &img, const PeerId &peer);
+	PhotoCropBox(const QImage &img, PeerData *peer);
 	void keyPressEvent(QKeyEvent *e);
 	void paintEvent(QPaintEvent *e);
 	void resizeEvent(QResizeEvent *e);
@@ -53,6 +54,8 @@ protected:
 
 private:
 
+	void init(const QImage &img, PeerData *peer);
+
 	QString _title;
 	int32 _downState;
 	int32 _thumbx, _thumby, _thumbw, _thumbh;
diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp
index a6c6eb5d1..9b5640828 100644
--- a/Telegram/SourceFiles/dropdown.cpp
+++ b/Telegram/SourceFiles/dropdown.cpp
@@ -2740,7 +2740,7 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
 
 			const BotCommand *command = _crows->at(i).second;
 			QString toHighlight = command->command;
-			int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : -1;
+			int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
 			if (hasUsername || botStatus == 0 || botStatus == 2) {
 				toHighlight += '@' + user->username;
 			}
@@ -2828,7 +2828,7 @@ QString MentionsInner::getSelected() const {
 		} else {
 			UserData *user = _crows->at(_sel).first;
 			const BotCommand *command(_crows->at(_sel).second);
-			int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : -1;
+			int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
 			if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 1) {
 				result = '/' + command->command + '@' + user->username;
 			} else {
@@ -3004,6 +3004,19 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 				rows.push_back(i.value());
 			}
 		}
+	} else if (_filter.at(0) == '@' && _channel && _channel->isMegagroup()) {
+		QMultiMap<int32, UserData*> ordered;
+		if (_channel->mgInfo->lastParticipants.isEmpty()) {
+			if (App::api()) App::api()->requestLastParticipants(_channel);
+		} else {
+			rows.reserve(_channel->mgInfo->lastParticipants.size());
+			for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) {
+				UserData *user = *i;
+				if (user->username.isEmpty()) continue;
+				if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
+				rows.push_back(user);
+			}
+		}
 	} else if (_filter.at(0) == '#') {
 		const RecentHashtagPack &recent(cRecentWriteHashtags());
 		hrows.reserve(recent.size());
@@ -3019,7 +3032,6 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 			if (_chat->noParticipantInfo()) {
 				if (App::api()) App::api()->requestFullPeer(_chat);
 			} else if (!_chat->participants.isEmpty()) {
-				int32 index = 0;
 				for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
 					UserData *user = i.key();
 					if (!user->botInfo) continue;
@@ -3033,10 +3045,23 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 			if (!_user->botInfo->inited && App::api()) App::api()->requestFullPeer(_user);
 			cnt = _user->botInfo->commands.size();
 			bots.insert(_user, true);
+		} else if (_channel && _channel->isMegagroup()) {
+			if (_channel->mgInfo->bots.isEmpty()) {
+				if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestLastParticipants(_channel);
+			} else {
+				for (MegagroupInfo::Bots::const_iterator i = _channel->mgInfo->bots.cbegin(), e = _channel->mgInfo->bots.cend(); i != e; ++i) {
+					UserData *user = i.key();
+					if (!user->botInfo) continue;
+					if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
+					if (user->botInfo->commands.isEmpty()) continue;
+					bots.insert(user, true);
+					cnt += user->botInfo->commands.size();
+				}
+			}
 		}
 		if (cnt) {
 			crows.reserve(cnt);
-			int32 botStatus = _chat ? _chat->botStatus : -1;
+			int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1);
 			if (_chat) {
 				for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
 					UserData *user = *i;
@@ -3196,6 +3221,14 @@ ChatData *MentionsDropdown::chat() const {
 	return _chat;
 }
 
+ChannelData *MentionsDropdown::channel() const {
+	return _channel;
+}
+
+UserData *MentionsDropdown::user() const {
+	return _user;
+}
+
 int32 MentionsDropdown::innerTop() {
 	return _scroll.scrollTop();
 }
diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h
index 6daff9822..25304c82c 100644
--- a/Telegram/SourceFiles/dropdown.h
+++ b/Telegram/SourceFiles/dropdown.h
@@ -649,6 +649,8 @@ public:
 
 	const QString &filter() const;
 	ChatData *chat() const;
+	ChannelData *channel() const;
+	UserData *user() const;
 
 	int32 innerTop();
 	int32 innerBottom();
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 997dbb9ad..11cfd3359 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -133,14 +133,14 @@ namespace {
 }
 
 const TextParseOptions &itemTextOptions(History *h, PeerData *f) {
-	if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isChannel() && h->peer->asChannel()->botStatus >= 0)) {
+	if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) {
 		return _historyBotOptions;
 	}
 	return _historyTextOptions;
 }
 
 const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f) {
-	if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isChannel() && h->peer->asChannel()->botStatus >= 0)) {
+	if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) {
 		return _historyBotNoMonoOptions;
 	}
 	return _historyTextNoMonoOptions;
@@ -684,7 +684,9 @@ void ChannelHistory::addNewGroup(const MTPMessageGroup &group) {
 }
 
 HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) {
-	if (_joinedMessage || !peer->asChannel()->amIn()) return _joinedMessage;
+	if (_joinedMessage || !peer->asChannel()->amIn() || (peer->asChannel()->mgInfo && peer->asChannel()->mgInfo->migrateFrom)) {
+		return _joinedMessage;
+	}
 
 	UserData *inviter = (peer->asChannel()->inviter > 0) ? App::userLoaded(peer->asChannel()->inviter) : 0;
 	if (!inviter) return 0;
@@ -809,7 +811,9 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) {
 }
 
 void ChannelHistory::checkJoinedMessage(bool createUnread) {
-	if (_joinedMessage || peer->asChannel()->inviter <= 0) return;
+	if (_joinedMessage || peer->asChannel()->inviter <= 0 || (peer->asChannel()->mgInfo && peer->asChannel()->mgInfo->migrateFrom)) {
+		return;
+	}
 	if (isEmpty()) {
 		if (loadedAtTop() && loadedAtBottom()) {
 			if (insertJoinedMessage(createUnread)) {
@@ -857,6 +861,13 @@ void ChannelHistory::checkJoinedMessage(bool createUnread) {
 	}
 }
 
+void ChannelHistory::removeJoinedMessage() {
+	if (_joinedMessage) {
+		_joinedMessage->destroy();
+		_joinedMessage = 0;
+	}
+}
+
 void ChannelHistory::checkMaxReadMessageDate() {
 	if (_maxReadMessageDate.isValid()) return;
 
@@ -1713,33 +1724,54 @@ HistoryItem *History::addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *a
 		}
 	}
 	if (adding->from()->id) {
-		if (peer->isChat() && adding->from()->isUser()) {
-			QList<UserData*> *lastAuthors = &(peer->asChat()->lastAuthors);
-			int prev = lastAuthors->indexOf(adding->from()->asUser());
-			if (prev > 0) {
-				lastAuthors->removeAt(prev);
+		if (adding->from()->isUser()) {
+			QList<UserData*> *lastAuthors = 0;
+			if (peer->isChat()) {
+				lastAuthors = &peer->asChat()->lastAuthors;
+			} else if (peer->isMegagroup() && !peer->asChannel()->mgInfo->lastParticipants.isEmpty()) {
+				lastAuthors = &peer->asChannel()->mgInfo->lastParticipants;
 			}
-			if (prev) {
-				lastAuthors->push_front(adding->from()->asUser());
+			if (lastAuthors) {
+				int prev = lastAuthors->indexOf(adding->from()->asUser());
+				if (prev > 0) {
+					lastAuthors->removeAt(prev);
+				}
+				if (prev) {
+					lastAuthors->push_front(adding->from()->asUser());
+				}
 			}
 		}
 		if (adding->hasReplyMarkup()) {
 			int32 markupFlags = App::replyMarkup(channelId(), adding->id).flags;
 			if (!(markupFlags & MTPDreplyKeyboardMarkup::flag_selective) || adding->mentionsMe()) {
+				QMap<PeerData*, bool> *markupSenders = 0;
 				if (peer->isChat()) {
-					peer->asChat()->markupSenders.insert(adding->from(), true);
+					markupSenders = &peer->asChat()->markupSenders;
+				} else if (peer->isMegagroup()) {
+					markupSenders = &peer->asChannel()->mgInfo->markupSenders;
+				}
+				if (markupSenders) {
+					markupSenders->insert(adding->from(), true);
 				}
 				if (markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO) { // zero markup means replyKeyboardHide
-					if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->isChat() && !adding->out())) {
+					if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->isChat() && !peer->isMegagroup() && !adding->out())) {
 						clearLastKeyboard();
 					}
-				} else if (peer->isChat() && adding->from()->isUser() && (!peer->asChat()->canWrite() || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(adding->from()->asUser())) {
-					clearLastKeyboard();
 				} else {
-					lastKeyboardInited = true;
-					lastKeyboardId = adding->id;
-					lastKeyboardFrom = adding->from()->id;
-					lastKeyboardUsed = false;
+					bool botNotInChat = false;
+					if (peer->isChat()) {
+						botNotInChat = adding->from()->isUser() && (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(adding->from()->asUser());
+					} else if (peer->isMegagroup()) {
+						botNotInChat = adding->from()->isUser() && (!peer->canWrite() || !peer->asChannel()->mgInfo->bots.isEmpty()) && !peer->asChannel()->mgInfo->bots.contains(adding->from()->asUser());
+					}
+					if (botNotInChat) {
+						clearLastKeyboard();
+					} else {
+						lastKeyboardInited = true;
+						lastKeyboardId = adding->id;
+						lastKeyboardFrom = adding->from()->id;
+						lastKeyboardUsed = false;
+					}
 				}
 			}
 		}
@@ -1904,10 +1936,20 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPM
 			++skip;
 		}
 
-		if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors
+		if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants
 			bool channel = isChannel();
 			int32 mask = 0;
-			QList<UserData*> *lastAuthors = peer->isChat() ? &(peer->asChat()->lastAuthors) : 0;
+			QList<UserData*> *lastAuthors = 0;
+			QMap<PeerData*, bool> *markupSenders = 0;
+			if (peer->isChat()) {
+				lastAuthors = &peer->asChat()->lastAuthors;
+				markupSenders = &peer->asChat()->markupSenders;
+			} else if (peer->isMegagroup()) {
+				if (!peer->asChannel()->mgInfo->lastParticipants.isEmpty()) {
+					lastAuthors = &peer->asChannel()->mgInfo->lastParticipants;
+				}
+				markupSenders = &peer->asChannel()->mgInfo->markupSenders;
+			}
 			for (int32 i = block->items.size(); i > 0; --i) {
 				HistoryItem *item = block->items[i - 1];
 				if (!item->indexInOverview()) continue;
@@ -1932,16 +1974,24 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPM
 						if (item->from()->isUser() && !lastAuthors->contains(item->from()->asUser())) {
 							lastAuthors->push_back(item->from()->asUser());
 						}
-						if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // chats with bots
+					}
+					if (markupSenders) { // chats with bots
+						if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) {
 							int32 markupFlags = App::replyMarkup(channelId(), item->id).flags;
 							if (!(markupFlags & MTPDreplyKeyboardMarkup::flag_selective) || item->mentionsMe()) {
-								bool wasKeyboardHide = peer->asChat()->markupSenders.contains(item->from());
+								bool wasKeyboardHide = markupSenders->contains(item->from());
 								if (!wasKeyboardHide) {
-									peer->asChat()->markupSenders.insert(item->from(), true);
+									markupSenders->insert(item->from(), true);
 								}
 								if (!(markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO)) {
 									if (!lastKeyboardInited) {
-										if (wasKeyboardHide || ((!peer->asChat()->canWrite() || !peer->asChat()->participants.isEmpty()) && item->from()->isUser() && !peer->asChat()->participants.contains(item->from()->asUser()))) {
+										bool botNotInChat = false;
+										if (peer->isChat()) {
+											botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && item->from()->isUser() && !peer->asChat()->participants.contains(item->from()->asUser());
+										} else if (peer->isMegagroup()) {
+											botNotInChat = (!peer->canWrite() || !peer->asChannel()->mgInfo->bots.isEmpty()) && item->from()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->from()->asUser());
+										}
+										if (wasKeyboardHide || botNotInChat) {
 											clearLastKeyboard();
 										} else {
 											lastKeyboardInited = true;
@@ -2485,6 +2535,9 @@ void History::clear(bool leaveItems) {
 		peer->asChat()->markupSenders.clear();
 	} else if (isChannel()) {
 		asChannelHistory()->cleared();
+		if (isMegagroup()) {
+			peer->asChannel()->mgInfo->markupSenders.clear();
+		}
 	}
 	if (leaveItems && App::main()) App::main()->historyCleared(this);
 }
@@ -7450,6 +7503,10 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) {
 		const MTPDmessageActionChannelMigrateFrom &d(action.c_messageActionChannelMigrateFrom());
 		if (true/*PeerData *chat = App::peerLoaded(peerFromChannel(d.vchat_id))*/) {
 			text = lang(lng_action_group_migrate);
+			if (history()->peer->asChannel()->mgInfo) {
+				history()->peer->asChannel()->mgInfo->migrateFrom = App::chat(peerFromChat(d.vchat_id));
+				history()->peer->asChannel()->mgInfo->migrateFrom->migrateTo = history()->peer->asChannel();
+			}
 		} else {
 			text = lang(lng_contacts_loading);
 		}
diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h
index 1188553e6..849aaf894 100644
--- a/Telegram/SourceFiles/history.h
+++ b/Telegram/SourceFiles/history.h
@@ -398,6 +398,7 @@ public:
 
 	HistoryJoined *insertJoinedMessage(bool unread);
 	void checkJoinedMessage(bool createUnread = false);
+	void removeJoinedMessage();
 	const QDateTime &maxReadMessageDate();
 
 private:
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index 128f5e454..b05ada7e2 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -2921,7 +2921,7 @@ void HistoryWidget::applyDraft(bool parseLinks) {
 	}
 }
 
-void HistoryWidget::showPeerHistory(const PeerId &peerId, MsgId showAtMsgId) {
+void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId) {
 	MsgId wasMsgId = _showAtMsgId;
 	History *wasHistory = _history;
 
@@ -3811,9 +3811,9 @@ void HistoryWidget::onVisibleChanged() {
 
 void HistoryWidget::onHistoryToEnd() {
 	if (_replyReturn) {
-		showPeerHistory(_peer->id, _replyReturn->id);
+		showHistory(_peer->id, _replyReturn->id);
 	} else if (_peer) {
-		showPeerHistory(_peer->id, ShowAtUnreadMsgId);
+		showHistory(_peer->id, ShowAtUnreadMsgId);
 	}
 }
 
@@ -3830,7 +3830,7 @@ void HistoryWidget::onCollapseComments() {
 			}
 		}
 	}
-	showPeerHistory(_peer->id, switchAt);
+	showHistory(_peer->id, switchAt);
 }
 
 void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
@@ -4318,7 +4318,7 @@ void HistoryWidget::sendBotCommand(const QString &cmd, MsgId replyTo) { // reply
 	PeerData *bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? (App::hoveredLinkItem()->toHistoryForwarded() ? App::hoveredLinkItem()->toHistoryForwarded()->fromForwarded() : App::hoveredLinkItem()->from()) : 0);
 	if (bot && (!bot->isUser() || !bot->asUser()->botInfo)) bot = 0;
 	QString username = bot ? bot->asUser()->username : QString();
-	int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isChannel() ? _peer->asChannel()->botStatus : -1);
+	int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1);
 	if (!replyTo && toSend.indexOf('@') < 2 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) {
 		toSend += '@' + username;
 	}
@@ -4342,7 +4342,7 @@ void HistoryWidget::insertBotCommand(const QString &cmd) {
 	PeerData *bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? (App::hoveredLinkItem()->toHistoryForwarded() ? App::hoveredLinkItem()->toHistoryForwarded()->fromForwarded() : App::hoveredLinkItem()->from()) : 0);
 	if (!bot->isUser() || !bot->asUser()->botInfo) bot = 0;
 	QString username = bot ? bot->asUser()->username : QString();
-	int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isChannel() ? _peer->asChannel()->botStatus : -1);
+	int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1);
 	if (toInsert.indexOf('@') < 2 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) {
 		toInsert += '@' + username;
 	}
@@ -4444,16 +4444,7 @@ void HistoryWidget::updateDragAreas() {
 }
 
 bool HistoryWidget::canSendMessages(PeerData *peer) const {
-	if (peer) {
-		if (peer->isUser()) {
-			return peer->asUser()->access != UserNoAccess;
-		} else if (peer->isChat()) {
-			return peer->asChat()->canWrite();
-		} else if (peer->isChannel()) {
-			return peer->asChannel()->amIn() && (peer->asChannel()->canPublish() || !peer->asChannel()->isBroadcast());
-		}
-	}
-	return false;
+	return peer && peer->canWrite();
 }
 
 bool HistoryWidget::readyToForward() const {
@@ -4483,7 +4474,7 @@ bool HistoryWidget::isMuteUnmute() const {
 
 bool HistoryWidget::updateCmdStartShown() {
 	bool cmdStartShown = false;
-	if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isChannel() && _peer->asChannel()->botStatus > 0) || (_peer->isUser() && _peer->asUser()->botInfo))) {
+	if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->botInfo))) {
 		if (!isBotStart() && !isBlocked() && !_keyboard.hasMarkup() && !_keyboard.forceReply()) {
 			if (!_field.hasSendText()) {
 				cmdStartShown = true;
@@ -6347,7 +6338,7 @@ QRect HistoryWidget::historyRect() const {
 }
 
 void HistoryWidget::destroyData() {
-	showPeerHistory(0, 0);
+	showHistory(0, 0);
 }
 
 QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) {
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index 12cb3343b..4be2749ad 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -516,7 +516,7 @@ public:
 
 	void fastShowAtEnd(History *h);
 	void applyDraft(bool parseLinks = true);
-	void showPeerHistory(const PeerId &peer, MsgId showAtMsgId);
+	void showHistory(const PeerId &peer, MsgId showAtMsgId);
 	void clearDelayedShowAt();
 	void clearAllLoadRequests();
 
diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp
index ec938912a..fbb19cd45 100644
--- a/Telegram/SourceFiles/intro/introsignup.cpp
+++ b/Telegram/SourceFiles/intro/introsignup.cpp
@@ -88,7 +88,7 @@ void IntroSignup::mousePressEvent(QMouseEvent *e) {
 			showError(lang(lng_bad_photo));
 			return;
 		}
-		PhotoCropBox *box = new PhotoCropBox(img, 0);
+		PhotoCropBox *box = new PhotoCropBox(img, PeerId(0));
 		connect(box, SIGNAL(ready(const QImage &)), this, SLOT(onPhotoReady(const QImage &)));
 		App::wnd()->showLayer(box);
 	}
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 9491d81f0..c71064c44 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -2314,7 +2314,7 @@ void MainWidget::showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool back)
 		history.show();
 	}
 	if (history.peer() && history.peer()->id != peerId) clearBotStartToken(history.peer());
-	history.showPeerHistory(peerId, showAtMsgId);
+	history.showHistory(peerId, showAtMsgId);
 
 	bool noPeer = (!history.peer() || !history.peer()->id), onlyDialogs = noPeer && !cWideMode();
 	if (profile || overview) {
@@ -2487,7 +2487,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool
 	}
 	history.animStop();
 	if (back) clearBotStartToken(history.peer());
-	history.showPeerHistory(0, 0);
+	history.showHistory(0, 0);
 	history.hide();
 	if (!cWideMode()) dialogs.hide();
 
@@ -2533,7 +2533,7 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop)
 	profile->animShow(animCache, animTopBarCache, back, lastScrollTop);
 	history.animStop();
 	if (back) clearBotStartToken(history.peer());
-	history.showPeerHistory(0, 0);
+	history.showHistory(0, 0);
 	history.hide();
 
 	orderWidgets();
@@ -3639,7 +3639,7 @@ void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) {
 	switch (invite.type()) {
 	case mtpc_chatInvite: {
 		const MTPDchatInvite &d(invite.c_chatInvite());
-		ConfirmBox *box = new ConfirmBox((d.is_channel() ? lng_group_invite_want_join_channel : lng_group_invite_want_join)(lt_title, qs(d.vtitle)), lang(lng_group_invite_join));
+		ConfirmBox *box = new ConfirmBox(((d.is_channel() && !d.is_megagroup()) ? lng_group_invite_want_join_channel : lng_group_invite_want_join)(lt_title, qs(d.vtitle)), lang(lng_group_invite_join));
 		_inviteHash = hash;
 		connect(box, SIGNAL(confirmed()), this, SLOT(onInviteImport()));
 		App::wnd()->showLayer(box);
@@ -3954,7 +3954,7 @@ int32 MainWidget::dlgsWidth() const {
 }
 
 MainWidget::~MainWidget() {
-	if (App::main() == this) history.showPeerHistory(0, 0);
+	if (App::main() == this) history.showHistory(0, 0);
 
 	delete _background;
 
diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp
index f8ea8e98e..f3ef9a6e8 100644
--- a/Telegram/SourceFiles/profilewidget.cpp
+++ b/Telegram/SourceFiles/profilewidget.cpp
@@ -129,6 +129,9 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
 		if (chatPhoto && chatPhoto->date) {
 			_photoLink = TextLinkPtr(new PhotoLink(chatPhoto, _peer));
 		}
+		if (_peerChannel->isMegagroup() && _peerChannel->mgInfo->lastParticipants.isEmpty()) {
+			if (App::api()) App::api()->requestLastParticipants(_peerChannel);
+		}
 		_peerChannel->updateFull();
 	}
 
@@ -294,7 +297,7 @@ void ProfileInner::onUpdatePhoto() {
 		saveError(lang(lng_bad_photo));
 		return;
 	}
-	PhotoCropBox *box = new PhotoCropBox(img, _peer->id);
+	PhotoCropBox *box = new PhotoCropBox(img, _peer);
 	connect(box, SIGNAL(closed()), this, SLOT(onPhotoUpdateStart()));
 	App::wnd()->showLayer(box);
 }
@@ -369,9 +372,15 @@ bool ProfileInner::blockFail(const RPCError &error) {
 }
 
 void ProfileInner::onAddParticipant() {
-	if (!_peerChat) return;
-
-	App::wnd()->showLayer(new ContactsBox(_peerChat, MembersFilterRecent));
+	if (_peerChat) {
+		App::wnd()->showLayer(new ContactsBox(_peerChat, MembersFilterRecent));
+	} else if (_peerChannel && _peerChannel->mgInfo) {
+		MembersAlreadyIn already;
+		for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) {
+			already.insert(*i, true);
+		}
+		App::wnd()->showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already));
+	}
 }
 
 void ProfileInner::onMigrate() {
@@ -669,6 +678,28 @@ void ProfileInner::reorderParticipants() {
 			_onlineText = lng_chat_status_members(lt_count, _participants.size());
 		}
 		loadProfilePhotos(_lastPreload);
+	} else if (_peerChannel && _peerChannel->isMegagroup() && _peerChannel->amIn() && !_peerChannel->mgInfo->lastParticipants.isEmpty()) {
+		if (!_peerChannel->mgInfo->lastParticipants.isEmpty()) {
+			_participants.clear();
+			for (ParticipantsData::iterator i = _participantsData.begin(), e = _participantsData.end(); i != e; ++i) {
+				if (*i) {
+					delete *i;
+					*i = 0;
+				}
+			}
+			_participants.reserve(_peerChannel->mgInfo->lastParticipants.size());
+			_participantsData.resize(_peerChannel->mgInfo->lastParticipants.size());
+		}
+		UserData *self = App::self();
+		bool onlyMe = true;
+		for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) {
+			_participants.push_back(*i);
+		}
+		if (_peerChannel->mgInfo->lastParticipants.isEmpty()) {
+			if (App::api()) App::api()->requestLastParticipants(_peerChannel);
+		}
+		_onlineText = (_peerChannel->count > 0) ? lng_chat_status_members(lt_count, _peerChannel->count) : lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status);
+		loadProfilePhotos(_lastPreload);
 	} else {
 		_participants.clear();
 		if (_peerUser) {
@@ -744,10 +775,10 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
 		addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent);
 		p.setPen(st::black->p);
 		p.drawText(_left + st::profilePhotoSize + st::profileStatusLeft, top + st::profileStatusTop + st::linkFont->ascent, '@' + _peerUser->username);
-	} else if (_peerChannel && (_peerChannel->isPublic() || _amCreator)) {
+	} else if (_peerChannel && !_peerChannel->isMegagroup() && (_peerChannel->isPublic() || _amCreator )) {
 		addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent);
 	}
-	if (!_peerChannel || !_peerChannel->canViewParticipants()) {
+	if (!_peerChannel || !_peerChannel->canViewParticipants() || _peerChannel->isMegagroup()) {
 		p.setPen((_peerUser && App::onlineColorUse(_peerUser, l_time) ? st::profileOnlineColor : st::profileOfflineColor)->p);
 		p.drawText(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop + st::linkFont->ascent, _onlineText);
 	}
@@ -855,21 +886,21 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
 	p.drawText(_left + st::profileHeaderLeft, top + st::profileHeaderTop + st::profileHeaderFont->ascent, lang(lng_profile_actions_section));
 	top += st::profileHeaderSkip;
 
-	top += _searchInPeer.height();
+	top += _searchInPeer.height() + st::setLittleSkip;
 	if (_peerUser || _peerChat) {
-		top += st::setLittleSkip + _clearHistory.height();
+		top += _clearHistory.height() + st::setLittleSkip;
 	}
 	if (_peerUser || _peerChat || (_peerChannel->amIn() && !_amCreator)) {
-		top += st::setLittleSkip + _deleteConversation.height();
+		top += _deleteConversation.height();
 	}
 	if (_peerUser && peerToUser(_peerUser->id) != MTP::authedId()) {
 		top += st::setSectionSkip + _blockUser.height();
 	} else if (_peerChannel && _amCreator) {
-		top += st::setSectionSkip + _deleteChannel.height();
+		top += (_peerChannel->isMegagroup() ? 0 : (st::setSectionSkip - st::setLittleSkip)) + _deleteChannel.height();
 	}
 
 	// participants
-	if (_peerChat && _peerChat->amIn()) {
+	if ((_peerChat && _peerChat->amIn()) || (_peerChannel && _peerChannel->isMegagroup() && _peerChannel->amIn())) {
 		QString sectionHeader = lang(_participants.isEmpty() ? lng_profile_loading : lng_profile_participants_section);
 		p.setFont(st::profileHeaderFont->f);
 		p.setPen(st::profileHeaderColor->p);
@@ -905,10 +936,12 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
 					}
 					if (_amCreator) {
 						data->cankick = (user != App::self());
-					} else if (_peerChat->amAdmin()) {
+					} else if (_peerChat && _peerChat->amAdmin()) {
 						data->cankick = (user != App::self()) && (_peerChat->admins.constFind(user) == _peerChat->admins.cend()) && (peerFromUser(_peerChat->creator) != user->id);
+					} else if (_peerChannel && _peerChannel->amEditor()) {
+						data->cankick = (user != App::self()) && (_peerChannel->mgInfo->lastAdmins.constFind(user) == _peerChannel->mgInfo->lastAdmins.cend());
 					} else {
-						data->cankick = (user != App::self()) && (_peerChat->invitedByMe.constFind(user) != _peerChat->invitedByMe.cend());
+						data->cankick = (user != App::self()) && !_peerChannel && (_peerChat->invitedByMe.constFind(user) != _peerChat->invitedByMe.cend());
 					}
 				}
 				p.setPen(st::profileListNameColor->p);
@@ -971,21 +1004,20 @@ void ProfileInner::updateSelected() {
 		update(QRect(_left, _aboutTop, _width, _aboutHeight));
 	}
 
-	int32 partfrom = _searchInPeer.y() + _searchInPeer.height();
-	if (_peerUser || _peerChat) {
-		partfrom = _clearHistory.y() + _clearHistory.height();
+	int32 participantsTop = 0;
+	if (_peerChannel && _amCreator) {
+		participantsTop = _deleteChannel.y() + _deleteChannel.height();
+	} else {
+		participantsTop = _deleteConversation.y() + _deleteConversation.height();
 	}
-	if (_peerUser || _peerChat || (_peerChannel->amIn() && !_amCreator)) {
-		partfrom = _deleteConversation.y() + _deleteConversation.height();
-	}
-	partfrom += st::profileHeaderSkip;
-	int32 newSelected = (lp.x() >= _left - st::profileListPadding.width() && lp.x() < _left + _width + st::profileListPadding.width() && lp.y() >= partfrom) ? (lp.y() - partfrom) / _pHeight : -1;
+	participantsTop += st::profileHeaderSkip;
+	int32 newSelected = (lp.x() >= _left - st::profileListPadding.width() && lp.x() < _left + _width + st::profileListPadding.width() && lp.y() >= participantsTop) ? (lp.y() - participantsTop) / _pHeight : -1;
 
 	UserData *newKickOver = 0;
 	if (newSelected >= 0 && newSelected < _participants.size()) {
 		ParticipantData *data = _participantsData[newSelected];
 		if (data && data->cankick) {
-			int32 top = partfrom + newSelected * _pHeight + st::profileListNameTop;
+			int32 top = participantsTop + newSelected * _pHeight + st::profileListNameTop;
 			if ((lp.x() >= _left + _width - _kickWidth) && (lp.x() < _left + _width) && (lp.y() >= top) && (lp.y() < top + st::linkFont->height)) {
 				newKickOver = _participants[newSelected];
 			}
@@ -1056,9 +1088,12 @@ void ProfileInner::mouseReleaseEvent(QMouseEvent *e) {
 }
 
 void ProfileInner::onKickConfirm() {
-	if (!_peerChat) return;
-
-	App::main()->kickParticipant(_peerChat, _kickConfirm);
+	if (_peerChat) {
+		App::main()->kickParticipant(_peerChat, _kickConfirm);
+	} else if (_peerChannel) {
+		App::wnd()->hideLayer();
+		App::api()->kickParticipant(_peerChannel, _kickConfirm);
+	}
 }
 
 void ProfileInner::keyPressEvent(QKeyEvent *e) {
@@ -1299,12 +1334,12 @@ void ProfileInner::resizeEvent(QResizeEvent *e) {
 		top += st::setSectionSkip;
 		_blockUser.move(_left, top); top += _blockUser.height();
 	} else if (_peerChannel && _amCreator) {
-		top += st::setSectionSkip;
+		top += (_peerChannel->isMegagroup() ? 0 : (st::setSectionSkip - st::setLittleSkip));
 		_deleteChannel.move(_left, top); top += _deleteChannel.height();
 	}
 
 	// participants
-	if (_peerChat && _peerChat->amIn()) {
+	if ((_peerChat && _peerChat->amIn()) || (_peerChannel && _peerChannel->isMegagroup() && _peerChannel->amIn())) {
 		top += st::profileHeaderSkip;
 		if (!_participants.isEmpty()) {
 			int32 fullCnt = _participants.size();
@@ -1427,6 +1462,13 @@ int32 ProfileInner::countMinHeight() {
 		} else {
 			h = _searchInPeer.y() + _searchInPeer.height() + st::profileHeaderSkip;
 		}
+		if (_peerChannel->isMegagroup()) {
+			if (!_participants.isEmpty()) {
+				h += st::profileHeaderSkip + _participants.size() * _pHeight;
+			} else if (_peerChannel->amIn()) {
+				h += st::profileHeaderSkip;
+			}
+		}
 	}
 	return h;
 }
@@ -1555,7 +1597,11 @@ void ProfileInner::showAll() {
 				_invitationLink.hide();
 			}
 		}
-		_addParticipant.hide();
+		if (_peerChannel->count < cMaxMegaGroupCount() && _peerChannel->isMegagroup() && (_amCreator || _peerChannel->amEditor())) {
+			_addParticipant.show();
+		} else {
+			_addParticipant.hide();
+		}
 		_blockUser.hide();
 		if (_amCreator) {
 			_deleteChannel.show();
@@ -1572,7 +1618,7 @@ void ProfileInner::showAll() {
 		} else {
 			_admins.hide();
 		}
-		if (_peerChannel->canViewParticipants()) {
+		if (_peerChannel->canViewParticipants() && !_peerChannel->isMegagroup()) {
 			_members.show();
 		} else {
 			_members.hide();
diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp
index 853c9845e..4b3b8264a 100644
--- a/Telegram/SourceFiles/settingswidget.cpp
+++ b/Telegram/SourceFiles/settingswidget.cpp
@@ -1169,7 +1169,7 @@ void SettingsInner::onUpdatePhoto() {
 		saveError(lang(lng_bad_photo));
 		return;
 	}
-	PhotoCropBox *box = new PhotoCropBox(img, self()->id);
+	PhotoCropBox *box = new PhotoCropBox(img, self());
 	connect(box, SIGNAL(closed()), this, SLOT(onPhotoUpdateStart()));
 	App::wnd()->showLayer(box);
 }
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index fbb2390ea..5c4490567 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -1026,6 +1026,7 @@ void CommentsLink::onClick(Qt::MouseButton button) const {
 }
 
 MsgId clientMsgId() {
-	static MsgId current = -2000000000;
-	return ++current;
+	static MsgId currentClientMsgId = StartClientMsgId;
+	Q_ASSERT(currentClientMsgId < EndClientMsgId);
+	return currentClientMsgId++;
 }
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 6ef82fade..97d639641 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -142,8 +142,14 @@ inline bool operator<(const FullMsgId &a, const FullMsgId &b) {
 	return a.channel < b.channel;
 }
 
+static const MsgId StartClientMsgId = -0x7FFFFFFF;
+static const MsgId EndClientMsgId = -0x40000000;
+inline bool isClientMsgId(MsgId id) {
+	return id >= StartClientMsgId && id < EndClientMsgId;
+}
 static const MsgId ShowAtTheEndMsgId = -0x40000000;
 static const MsgId SwitchAtTopMsgId = -0x3FFFFFFF;
+static const MsgId ServerMaxMsgId = 0x3FFFFFFF;
 static const MsgId ShowAtUnreadMsgId = 0;
 
 struct NotifySettings {
@@ -212,6 +218,7 @@ public:
 	}
 	bool isVerified() const;
 	bool isMegagroup() const;
+	bool canWrite() const;
 	UserData *asUser();
 	const UserData *asUser() const;
 	ChatData *asChat();
@@ -350,6 +357,9 @@ public:
 	bool isVerified() const {
 		return flags & MTPDuser::flag_verified;
 	}
+	bool canWrite() const {
+		return access != UserNoAccess;
+	}
 
 	MTPInputUser inputUser;
 
@@ -373,7 +383,7 @@ public:
 class ChatData : public PeerData {
 public:
 
-	ChatData(const PeerId &id) : PeerData(id), inputChat(MTP_int(bareId())), count(0), date(0), version(0), creator(0), inviterForSpamReport(0), flags(0), isForbidden(false), botStatus(0) {
+	ChatData(const PeerId &id) : PeerData(id), inputChat(MTP_int(bareId())), migrateTo(0), count(0), date(0), version(0), creator(0), inviterForSpamReport(0), flags(0), isForbidden(false), botStatus(0) {
 	}
 	void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = UnknownPeerPhotoId);
 	void invalidateParticipants() {
@@ -389,6 +399,8 @@ public:
 
 	MTPint inputChat;
 
+	ChannelData *migrateTo;
+
 	int32 count;
 	int32 date;
 	int32 version;
@@ -504,19 +516,24 @@ private:
 };
 
 struct MegagroupInfo {
-	MegagroupInfo() : botStatus(-1) {
+	MegagroupInfo() : botStatus(-1), migrateFrom(0) {
 	}
 	typedef QList<UserData*> LastParticipants;
 	LastParticipants lastParticipants;
+	typedef QMap<UserData*, bool> LastAdmins;
+	LastAdmins lastAdmins;
 	typedef QMap<PeerData*, bool> MarkupSenders;
 	MarkupSenders markupSenders;
+	typedef QMap<UserData*, bool> Bots;
+	Bots bots;
 	int32 botStatus; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
+	ChatData *migrateFrom;
 };
 
 class ChannelData : public PeerData {
 public:
 
-	ChannelData(const PeerId &id) : PeerData(id), access(0), inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))), count(1), adminsCount(1), date(0), version(0), flags(0), flagsFull(0), mgInfo(0), isForbidden(true), botStatus(-1), inviter(0), _lastFullUpdate(0) {
+	ChannelData(const PeerId &id) : PeerData(id), access(0), inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))), count(1), adminsCount(1), date(0), version(0), flags(0), flagsFull(0), mgInfo(0), isForbidden(true), inviter(0), _lastFullUpdate(0) {
 		setName(QString(), QString());
 	}
 	void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = UnknownPeerPhotoId);
@@ -566,6 +583,9 @@ public:
 	bool canPublish() const {
 		return amCreator() || amEditor();
 	}
+	bool canWrite() const {
+		return amIn() && (canPublish() || !isBroadcast());
+	}
 	bool canViewParticipants() const {
 		return flagsFull & MTPDchannelFull::flag_can_view_participants;
 	}
@@ -574,7 +594,6 @@ public:
 		return flags & MTPDchannel::flag_verified;
 	}
 
-	int32 botStatus; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
 //	ImagePtr photoFull;
 	QString invitationUrl;
 
@@ -655,6 +674,9 @@ inline bool PeerData::isVerified() const {
 inline bool PeerData::isMegagroup() const {
 	return isChannel() ? asChannel()->isMegagroup() : false;
 }
+inline bool PeerData::canWrite() const {
+	return isChannel() ? asChannel()->canWrite() : (isChat() ? asChat()->canWrite() : (isUser() ? asUser()->canWrite() : false));
+}
 
 inline int32 newMessageFlags(PeerData *p) {
 	return p->isSelf() ? 0 : (((p->isChat() || (p->isUser() && !p->asUser()->botInfo)) ? MTPDmessage::flag_unread : 0) | MTPDmessage::flag_out);