Когда я писал проект crafthunters.com, я заметил что для раскрутки клиенты используют социальные сети. Пользовались виджетами и лайками, но по хорошему надо было попадать в ленту новостей. Кроме того, популярный вконтакте вывел новости на главную страницу в сентябре. Т.е. для распространения контента надо было адаптировать standalone блог для представления в социальных сетях, используя простую истину: попасть в ленту новостей популярных социальных сетей. Вначале это происходило вручную и приносило более половины траффика. Потом пришла идея это всё автоматизировать.
Популярными социальными сетями у нас были:
- вконтакте
- 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, но необходимо будет прописать свои.