Pull to refresh

Кросспостинг в twitter, facebook, livejournal, vkontakte

ASP *
Tutorial

Когда я писал проект crafthunters.com, я заметил что для раскрутки клиенты используют социальные сети. Пользовались виджетами и лайками, но по хорошему надо было попадать в ленту новостей. Кроме того, популярный вконтакте вывел новости на главную страницу в сентябре. Т.е. для распространения контента надо было адаптировать standalone блог для представления в социальных сетях, используя простую истину: попасть в ленту новостей популярных социальных сетей. Вначале это происходило вручную и приносило более половины траффика. Потом пришла идея это всё автоматизировать.
Популярными социальными сетями у нас были:
  • facebook
  • вконтакте
  • livejournal
  • твиттер

Предстояло настроить автоматический кросспостинг в каждую из социальных сетей, который до этого вели вручную. Нужно еще уточнить, что размещать записи в социальных сетях необходимо было не у себя на стене\бложике, а в группе\сообществе.

Далее я хочу представить реализацию кросспостинга под эти четыре социальные сети для asp.net mvc.


0. Основные принципы.



Перво-наперво необходимо получить права и сохранить их в базе данных для последующего использования. Вторым шагом было установка в какую группу\сообщество будет добавляться контент (задача по сути только для вконтакта). Третьим шагом было создание записи в группе\сообществе с активной ссылкой на оригинальный пост.

Для твиттера, фейсбука и вконтакте используется API на основе OAuth и для работы с ними надо было зарегистировать приложение в каждой социальной сети для этих целей.

Чтобы не повторяться, я сразу расскажу как происходит работа c OAuth (для twitter xAuth). Вначале мы передаем id приложения и список прав, которые хотим получить. Список прав — это к каким ресурсам мы разрешаем доступ нашего приложения. Выглядит это так:



После разрешения, мы получаем код по которому получаем токен доступа. К каждому обращению на сервер мы передаем этот токен и тем самым провайдер услуг (вконтакте, фейсбук и твиттер) знают что нам можно доверять и разрешают совершить действие (в нашем случае — кросспостинг).

1. Livejournal



ЖЖ уже давно используется для кросспостинга и делает он это через XML-RPC команды.
Почитав документацию (http://www.livejournal.com/doc/server/ljp.csp.xml-rpc.protocol.html) использовав библиотеку для .net xml-rpc (http://www.xml-rpc.net/) я приступил к реализации.

Создаем интерфейс взаимодействия:

public interface ILj : IXmlRpcProxy 
{   
	[XmlRpcMethod("LJ.XMLRPC.login")]   
	LjUserInfo Login(UserPassword user); 

	XmlRpcMethod("LJ.XMLRPC.postevent")] 
	PostLjAnswer Post(PostLj post); 
}


Получение доступа



Логин и пароль передаются в открытом виде и передаются по протоколу веб-сервиса. Создаем класс для получения прав.
public class UserPassword
{   
	[JsonProperty("username")]
	public string username { get; set; } 

	[JsonProperty("password")]
	public string password { get; set; }   

	public int ver   {
		get     {
			return 1; 
		} 
	} 
}


При каждой публикации логин и пароль также передаются, так что метод Login по сути только проверяет правильность пары логин\пароль.

Проверка доступа.


public LjUserInfo Auth(UserPassword username) 
{ 
	ILj proxy = XmlRpcProxyGen.Create<ILj>(); 
	var ans = proxy.Login(username);
	return ans; 
} 

Ответ ans мы анализируем на положительный ответ иначе выдаем ошибку.

Добавление записи столь же тривиально:
public void Publish(UserPassword username, Post message, string ljgroup = null) 
{ 
	ILj proxy = XmlRpcProxyGen.Create<ILj>();  
	var post = new PostLj();   
	post.username = username.username;   
	post.password = username.password;   
	post.ver = 1;   
	post.@event = message.Content;   
	post.subject = message.Title;   
	post.lineendings = "pc";   
	post.year = DateTime.Now.Year;   
	post.mon = DateTime.Now.Month;   
	post.day = DateTime.Now.Day;   
	post.hour = DateTime.Now.Hour;   
	post.min = DateTime.Now.Minute;   
	if (!string.IsNullOrWhiteSpace(ljgroup))   {  
		post.usejournal = ljgroup;   
	}   else   {     
		post.usejournal = username.username;   
	}   
	var ans = proxy.Post(post); 
}

Собственно всё очень просто. Конечно, есть очень много дополнительных опций и библиотека достаточно обширна, но задача — добавить наш пост в ЖЖ — решается легко.

Что не понравилось — передача пароля в открытом виде или в md5 по незащищеному каналу.

2. Твиттер



Для твиттера я использовал популярную библиотеку www.twitterizer.net но для начала надо было зарегистрировать приложение по адресу: dev.twitter.com/apps.

Права которые будет запрашивать приложение — как на чтение так и на запись. Кроме всего прочего, приложение получает бесконечный по сроку истечения токен для использования. Этот токен необходимо сохранить в БД. (Таблица Social.JsonResource)

Получение прав присходит по следующему сценарию:
public string Authorize(string redirectTo) 
{   
	OAuthTokenResponse requestToken = OAuthUtility.GetRequestToken(Config.twitterConsumerKey, Config.twitterConsumerSecret, redirectTo);   
	// Direct or instruct the user to the following address:   
	Uri authorizationUri = OAuthUtility.BuildAuthorizationUri(requestToken.Token);   
	return authorizationUri.ToString(); 
} 

Мы передаем два ключа (ключ приложения и секретный ключ), и переходим на страницу твиттера для подтверждения прав. Твиттер запрашивает права и возвращает нас по адресу redirectTo куда дополнительно передает код. По этому коду мы получаем токен доступа. Далее мы обновляем статус (создаем короткое сообщение) через приложение:
public void Publish(Post post) 
{   
	var tokens = new OAuthTokens();   
	tokens.ConsumerKey = Config.twitterConsumerKey;   
	tokens.ConsumerSecret = Config.twitterConsumerSecret;   
	tokens.AccessToken = twitterAccessToken.Token;   
	tokens.AccessTokenSecret = twitterAccessToken.TokenSecret;   
	TwitterStatus.Update(tokens, post.TwitterText); 
}


3. Facebook



Регистрируем приложение по адресу: developers.facebook.com/apps

Получаем токен доступа. Вначале отправляем для получения разрешения прав:
public string Authorize(string redirectTo) 
{   
	return string.Format(AuthorizeUri, Config.AppId, redirectTo); 
} 

public ActionResult GetFbCode() 
{   
	var fbSocial = currentUser.Socials.Where(p => p.Provider == "facebook").FirstOrDefault();   
	if (fbSocial != null)   {
		return RedirectToAction("Index");  
	}   else   {    
		return Redirect(fbProvider.Authorize("http://" + HostName + "/Social/SaveFbCode"));   
	} 
}

Обрабатываем код для получения токена доступа:
public ActionResult SaveFbCode() 
{   
	if (Request.Params.AllKeys.Contains("code")) {
		var code = Request.Params["code"];     
		if (ProcessFbCode(code))     
		{
			return RedirectToAction("Index");     
		}   
	}   
	return View("CantInitialize"); 
} 

protected bool ProcessFbCode(string code) {   
	if (fbProvider.GetAccessToken(code, "http://" + HostName + "/Social/SaveFbCode"))   
	{ 
		var jObj = fbProvider.GetUserInfo();    
		var fbUserInfo = JsonConvert.DeserializeObject<FbUserInfo>(jObj.ToString()); 
		var fbAccess = new FbAccessToken() 
		{ 
			AccessToken = fbProvider.AccessToken     
		};
		var jsonFbAccess = JsonConvert.SerializeObject(fbAccess);   
		var fbSocial = currentUser.Socials.Where(p => p.Provider == "facebook").FirstOrDefault();  
		if (fbSocial == null)  
		{      
			fbSocial = new Models.Social() 
			{       
				UserID = currentUser.ID,   
				JsonResource = jsonFbAccess.ToString(),
				Provider = "facebook",       
				UserInfo = jObj.ToString()    
			};  
			repository.CreateSocial(fbSocial);     
		}   
		else     
		{  
			fbSocial.UserInfo = jObj.ToString();      
			repository.UpdateSocial(fbSocial);  
		}   
		return true; 
	}  
	return false; 
}

Тут важная деталь при получении бессрочного токена доступа мы в параметре запроса параметров должны запросить &scope=...,offline тем самым избавив себя запрашивать этот токен постоянно, но для большей секретности этот фрагмент можно переделать и для использования токена с истекаемым сроком годности.

Создание поста


public ActionResult CrossPostFb(int id) 
{   
	var post = repository.Posts.Where(p => p.ID == id).FirstOrDefault();  
	var fbSocial = currentUser.SocialGetByProvider("facebook"); 
	if (post != null && post.UserID == currentUser.ID && fbSocial != null)  
	{    
		var postSocial = new Social.Post();     
		if (!string.IsNullOrWhiteSpace(post.PreviewUrl))  
		{ 
			//ссылка на изображение
			postSocial.Preview = "http://" + HostName + post.PreviewUrl; 
		} 
		postSocial.Title = post.Title; 
		postSocial.Teaser = post.Subtitle;     //ссылка на оригинал статьи 
		postSocial.Link = "http://" + HostName + "/Post/" + post.ID.ToString();     //устанавливаем сохраненые токены 
		var fbAccess = JsonConvert.DeserializeObject<FbAccessToken>(fbSocial.JsonResource);   
		fbProvider.AccessToken = fbAccess.AccessToken;  
		//публикуем пост  
		fbProvider.Publish(postSocial);   
		repository.CrossPost(post, Post.CrossProvider.facebook); 
	}  
	return RedirectToAction("Index");
}

В зависимости от установленных параметров создастся запись. При установке активной ссылки facebook самостоятельно выберет нужную ему картинку

4. Вконтакте



Вконтакте предлагает самый непростой алгоритм взаимодействия.

Начнем по порядку: создавать посты как у себя на стене так и на стене группы может только Standalone приложение (а не веб), поэтому регистрировать приложение (http://vkontakte.ru/editapp?act=create&site=1) нужно указывать [x] Standalone-приложение.

Второе: для добавления к записи картинки ее нужно загрузив предварительно.

Третье: для загрузки картинки необходимо запросить адрес сервера по которому будет производится загрузка.

Четвертое: ссылка на оригинальный пост размещается в параметре attachments и при добавлении ссылки не гарантирует добавление заметки.

Собственно остальное всё так же, так что приступим и получим права:
public ActionResult GetVkCode()
{ 
	var vkSocial = currentUser.Socials.Where(p => p.Provider == "vkontakte").FirstOrDefault();  
	if (vkSocial != null)  
	{    
		return RedirectToAction("Index"); 
	} 
	else 
	{    
		return Redirect(vkProvider.Authorize("http://" + HostName + "/Social/SaveVkCode"));   
	} 
} 

public string Authorize(string redirectTo) 
{   
	return string.Format(AuthorizeUri, Config.AppKey, redirectTo); 
}


Для нашего кросспостинга мы запрашиваем следующие уровни доступа: photos, groups, wall, offline.
Т.е. можем загружать фоточки в группы на стену в любое время.

Публикация



Перед публикацией на стену группы нам нужно узнать номер этой группы по имени (groups.getById). Удивительно то, что группы (а также персональные страницы) нумеруются в отрицательную сторону. Т.е.
после получения результата значение gid надо умножить на -1.

Потом мы запрашиваем сервер для загрузки фотографий: photos.getWallUploadServer, а не photos.getUploadServer — хоть они и идентичны почти.

Далее мы через post по полученному url-запросу посылаем фотографию на сервер вконтакте. Я использовал библиотеку UploadHelper (http://aspnetupload.com/).

После загрузки на сервер надо передать команду о сохранении этого изображения: photos.saveWallPhoto — на что получаем id фотографии. Если вы использовали photos.getUploadServer вместо photos.getWallUploadServer — то фотография не сохранится.

И уже следующим этапом мы добавляем фотографию на стену группы/или персональной страницы (wall.post).

Собственно всё.

5. Плюшки



Попробовать можно тут: http://cocosanka.ru (после регистрации, можно записать что-то бессмысленное в блог, и потом откросспостить).

Скачать исходники тут: https://bitbucket.org/chernikov/cocosanka2

Для работы с данными я использовал библиотеку Json.net(http://json.codeplex.com/) переводя автоматически полученные строки в объекты.

Ключи приложений хранятся в Web.config, но необходимо будет прописать свои.
Tags:
Hubs:
Total votes 39: ↑34 and ↓5 +29
Views 80K
Comments Comments 31