
Когда я писал проект 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, но необходимо будет прописать свои.