Из Facebook – в Livejournal, Twitter и Вконтакте, кросспостинг в исходных кодах и ошибках
В этой статье я расскажу о своем опыте разработки кросспостинга из моего Facebook в мой Livejournal (далее – ЖЖ), а также поделюсь исходными текстами, готовыми к запуску на ваших аккаунтах.
использование записи скриптов поиск по своим записям – возможность, которую Facebook никак не может воспроизводить как часть своего сервиса, а также «оживление» своего ЖЖ. Поиск доступа к широким постам в Фейсбуке требует обязательной авторизации, поисковых сервисов роботов, очевидно, не пускает.Конкретно в моем случае это неудобно: ссылки, видео и мысли, которые я имею в виду в соцсети, часто я обращаю внимание на «на будущее» – и часто обнаруживаю тот момент, когда эта информация становится передачей, но ее уже практически не найти.
Также с использованием опубликованных здесь скриптов удалось архив исторических записей Facebook: более 2000 архивных сообщений моего Facebook перешли в ЖЖ с соответствующими датами. То есть, если у вас еще не было ЖЖ, его можно сразу наполнить информацией за все время.
Также в статье выкладываю готовые скрипты на Perl, с помощью которых можно транслировать статусы Facebook в Livejournal, а оттуда, при наличии соответствующих настроек, в Вконтакте, Twitter и RSS, а также с использованием дополнительных веб-сервисов – практически во всех блог-движках.
Преамбула
Так получилось, что я два года назад ушел из ЖЖ в Facebook. Это было связано с кучей проблем в самой ЖЖ, заторможенностью с возможным использованием, и, как следствие, увеличением изъятия моих друзей в сине-белую соцсеть.
При этом ЖЖ остается единственной открытой блог-площадкой с гибким форматом постов, не накладывающей существенных технических ограничений – на размер поста или ни на его оформление. В ЖЖ до сих пор остается масса интересных личностей, из множества моих друзей.
Очень большое преимущество ЖЖ было в его открытости поисковым системам. То, что я могу потом написать в блоге, я нашел через «поисковики», где по практике индекс Яндекса или Гугла обновляется спустя несколько минут от публикации поста. Понимание того, что твоя статья, заметка или находка может быть полезной не только твоим друзьям, неплохо мотивирует на то, чтобы туда это полезно писать еще и еще. Впрочем, такая же логика работает и в отношении Хабры.
Я изучил сервисы кросспостинга, автоматически размещая сообщения в нескольких соцсетях. К сожалению, ничего толкового из них не нашлось.Почти везде принуждают писать посты в специальном интерфейсе, либо в обычном местеиспользуют ЖЖ, что лично для меня неудобно. Самым любопытным сервисом оказался стартап IFTTT.com, возникают универсальные правила вида «появилось в твиттере – отправь на смс», «появилось в фейсбуке – отправил в твиттер», «собирается дождь – предупреди по смс» и т.д. Да, его можно приспособить для некоторых нужд, но из фейсбука в ЖЖ он все равно постить не умеет. Во всех сервисах есть один большой недостаток – они слишком универсальны и плохо настраиваемы, да и редкие из них включают популярные российские соцсети. Тот же ЖЖ в списке встречается крайне редко. И если получить сообщение в ЖЖ можно через RSS, то отправить его можно только через API.
Есть и еще одна причина. Я хочу иметь возможность управлять тем, что куда постится, в зависимости от нагрузки. Например, я могу найти нужных автоматически публиковать в ЖЖ, а не посилання на ее фотографии. Или в твиттер переносить аллергию на пост, а не посилан на него, как это делают многие сервисы. Для этого я должен иметь возможность редактировать скрипт для себя, под свои нужды.
В конце концов, стояла цель о захвате соцсети Twitter, ЖЖ, Facebook и Вконтакте, оставив Facebook «стартовой площадкой» для поста. Посмотреть сам ЖЖ умеет постить в Вконтакте и в Твиттер, а также экспортировать посты в RSS для оценки с Drupal, нарисовалась следующая схема:
Про API
Facebook имеет, на мой взгляд, самое шикарное API. Этот интерфейс позволяет делать то, что необходимо с данными, которые вы вносите в соцсеть – получать их в структурированном виде, изменять или удалять через массу удобных механизмов, FQL, HTTP-запросы. И на этом фоне неожиданно возникло относительно небольшое количество внешних приложений, работающих с Facebook.
Кроме использования API, я также пробовал парсинг страниц упрощенной мобильной версии Facebook-а – это перемещает вытащить больше информации, чем дает API. В некоторых случаях это довольно полезный механизм.В случае успеха получилось вполне нормально.
В библиотеке для Perl все представлено прекрасно: для Perl на CPAN обнаружено несколько модулей, реализующих работу с Facebook, но из-за простоты протоколов нужды в них мало. Данные запроса передаются через URL, результат возвращается в формате JSON. Что касается ЖЖ, то он имеет несколько разных API, из которых встречается LJ XML-RPC. Я воспользовался готовым модулем для Perl, реализующим довольно стабильную работу с ЖЖ – LJ::Simple.
Особенности кросспостинга из Facebook
Доступ к фейсбуку осуществляется через токен доступа, доступный распределенному приложению на ограниченное время с ограниченными правами. Время протухания сессии и полученного access_token измеряется от 2 до 25 часов. Есть возможность получить долгоживущий токен со сроком жизни до 60 дней. По логике, необходимо запрашивать access_token после протухания каждый раз – через 5 дней это изображение или через 60. В включенных скриптах быстрое обновление не предусмотрено, как и о том, что протухает токен доступа.
Вторая особенность кроется в том, что ваши сообщения появляются на стене вместе с другими сообщениями юзеров, и если не задумываться о том, что кросспостится только у ваших томов, у других пользователей может появиться возможность опубликовать у вас в ЖЖ через фейсбук что-нибудь не то. По умолчанию в Facebook у всех друзей есть возможность публиковать вам на стену что угодно, особенно радует возможность тучу комментарии от друзей к чужой фотографии, на которых вас «отметили». Если не включать модерирование чужих сообщений на стену, то с появлением кросспостинга эти фото уходят еще и за пределами фейсбука.
Некоторые сообщения в фейсбуке являются «неинформативными» – как сообщения некоторыми, отправляемые приложениями. Кросспостер умеет их фильтровать и не переносить в Livejournal, но тут каждый настраивается под себя, конечно.Какие-то посты специально принимают другой вид: например, фотография превращается на Facebook во встроенную в пост на ЖЖ крупную фотографию.
Также требуется обязательное планирование простановки флага «задним числом» при публикации поста задним числом в ЖЖ. Для этого есть особая константа в начале скрипта. Особенность закономерности в том, что если вы следите за постом в ЖЖ за март 2011 года, то в ленту друзей он помещается как свежий (хоть и со старой датой), а при связке с твиттером, слушается и в нем как свежий. Если же установить в интерфейсе специальную галочку, или же установить свойство backdate через API, то из памяти он воспроизводится. Для переноса архива, полученного задним числом – обязательное дело, его можно открыть.
Ну и необходимо убрать связь с Facebook, иначе получится замкнутый цикл (в шаблоне на всякий случай защиты стоит).
Что получилось
Сюда я заслушиваю сценарии, разработанные в процессе исследования, эдакого подтверждения концепции. Также они выбраны для публикации в отдельных статьях – например, общие параметры подключения к БД, не вынесены в отдельные файлы, код, не разделенный по функциям и файлам, выбраны неважные и т.д.
Архитектура кросспостера предполагает двухэтапную работу: сохранение промежуточных результатов в БД и экспорт записей из БД в ЖЖ. В дальнейшем эта БД может использоваться как самостоятельная база, а также допустимые локальные данные легко дописывать скрипты для экспорта в другие социальные сети, RSS.
Для входа в БД используется скрипт facebook.pl. Его назначение – получить страницу со стены с Facebook и, если есть следующая страница, выдать ее идентификатор, если нет, выдать «все готово». Идентификатор является параметром скрипта, так что для загрузки страницы нужно скачать скрипт с идентификатором и т.д., пока в ответ не будет проведено расследование.
Обратите внимание, что для работы facebook.pl необходимо изменить параметры подключения к БД, ваш идентификатор журнала в ЖЖ, а также ввести access_token.Для отладки распространения короткоживущий токен доступа можно использовать в Facebook Graph API Explorer. Для того, чтобы получить доступ на 60 дней, необходимо создать приложение, AppId и SecretId, после того, как маркер доступа по выявленной ссылке выберет это приложение из ниспадающего списка. Обратите внимание на перечень прав – недостаток некоторых галочек может ограничить доступ к записям на вашей стене: например, внешнему приложению перестанут быть перепосты от других пользователей или фотографии или что-то другое. Если не боитесь в скриптах оставлять лишние доступы, лучше разместить вообще все галочки.
Для постинга на ЖЖ из БД используется скрипт update_lj.pl. В этой статье используется его отладочная версия – он берет из базы один вакантный пост, подготовленный ранее facebook.pl, отправляет его на ЖЖ, возвращает идентификатор страницы на ЖЖ, помечает пост как отправленный. Это промежуточная версия, и я здесь оставляю именно ее, потому что в случае каких-либо проблем удаляются из ЖЖ, созданные скриптом посты массово и очень неудобно.
На тот случай, если все-таки ЖЖ пополнился кучей автоматически созданных ошибочных постов, выборочным редактированием, удалением или изменением свойств может быть скриптом lj_change.pl, включенным в конце поста.
В конце концов, для переноса архива необходимо пройти по всем страницам bash-скриптом, вызывающим необходимое число раз facebook.pl, после чего проверка updatelj.pl столько раз, сколько у вас в базе получилось записей. Для регулярного обновления facebook.pl достаточно раз в час или раз в сутки передавать по крону, после аналогичного bash-скрипта распространяется updatelj.pl.
Буду рад общедоступным комментариям и дополнениям, а также тем энтузиастам, которые захотят или попытаются сделать из этого универсального универсального сервиса.
CREATE TABLE `myposts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ctime` datetime DEFAULT NULL, // время создания `message` text, // собственно пост `link` text, // ссылка к получается `picture_fb` text, // картинка к поступила `posted_to_lj` int(11) DEFAULT NULL, // было ли отправлено на ЖЖ? ПО УМОЛЧАНИЮ NULL, // идентификатор в ЖЖ (внутренний) `lj_anum` int(11) ПО УМОЛЧАНИЮ NULL, // константа для журнала, связывающая item_id и html_id `lj_html_id` int(11) ПО УМОЛЧАНИЮ NULL, // идентификатор в ЖЖ (внешний, тот , что перед .html) `user` varchar(50) DEFAULT NULL, // пользователь (для использования на базе нескольких пользователей) PRIMARY KEY (`id`) )
#!/bin/bash #для создания архива NEXT=`./facebook.pl`; эхо $NEXT; while [ "$NEXT" != "все готово" ] do NEXT=`$NEXT`; эхо $NEXT; Выполнено
Показать исходный текст facebook.pl
использовать open qw(:std:utf8);
использовать LWP :: Simple ;
использовать YAML :: Tiny;
использовать JSON;
использовать URI;
использовать ДБИ;
использовать DBD::MySQL;
$DB_LOGIN = "========DB-USER========= #339933">;
$DB_PASS = "========DB-PASS========= #339933">;
$DATABASE = "======== ИМЯ БД ========= #339933">;
$USER = '========ВАШ-ЖЖ-ПОЛЬЗОВАТЕЛЬ==========' ;
мой $access_token = '========ВАШ-ТОКЕН-ДОСТУПА-СМОТРЕТЬ-ГРАФ.FACEBOOK.COM-ДЛЯ-ДЕТАЛЕЙ==========' ;
# ARGV[0] — это значение, которое facebook помещает в URL своей следующей страницы в качестве значения CGI-параметра «до тех пор».
$до = $ARGV[0];
my $dbh = DBI -> connect ("DBI:mysql:database=mysql;host=localhost", $DB_LOGIN, $DB_PASS) || die "Ошибка подключения к базе данных: $!n" ;
$dbh -> делать ("использовать $DATABASE;");
# следующие четыре строки предназначены для запроса api графа facebook
мой $uri = новый URI ('https://graph.facebook.com/me/feed');
$uri -> query_form ( < access_token => $ access_token , until => $until , base_amount => 1 , value => 1 > ) ;
мой $resp = получить ("$uri");
$resp = определено $resp ? decode_json($resp): undef;
# в результате работы скрипта я решил показать либо сообщение all done, либо команду bash для следующей итерации. Выглядит немного странно…
если ( $until != "" ) <
напечатать "./facebook.pl" . $ до . "н" ;
> еще
<
напечатать «все готово»;
>
# обновление базы данных
для моего $post ( @ < $ resp -> < data >> ) <
$ctime = $post -> <созданное_время>;
$ctime =~ /(дддд)-(дд)-(дд).(дд):(дд):(дд)/ ;
($y, $m, $d, $h, $i, $s) = ($1, $2, $3, $4, $5, $6);
$time = "$h:$i:$s" ;
$sqltime = $у. $ м . $ д . $ч . $я. $с;
$сообщение = $сообщение -> <сообщение>;
$ссылка = $сообщение -> <ссылка>;
$picture = $post -> <картинка>;
$sql = "выберите * из моих сообщений, где user='$USER' и ctime='$sqltime'" ;
$sth = $dbh -> подготовить ($sql);
$sth -> выполнить ;
если ($sth -> строки == 0)
$sql = "вставить в мои посты набор user='$USER', ссылка #339933">. $dbh -> цитата ($ссылка). ", picture_fb #339933">. $dbh -> цитата ($картинка). ", сообщение #339933">. $dbh -> цитата ($сообщение). ", ctime='$sqltime'" ;
# следующая строка предназначена для фильтрации постов, похожих на твиттер. Я решил пропустить их, потому что большинство из них являются ссылками на существующие посты в фейсбуке или живом журнале.
если ( $сообщение !~ / t . co // ) <
$dbh -> сделать ($sql);
>
>
>
Показать исходный код update_lj
использовать ЖЖ :: Простой ;
использовать Date :: Manip ;
использовать ДБИ;
использовать DBD::MySQL;
$DATABASE = 'facebook';
$DEBUG = 1;
# чрезвычайно важно установить следующую константу в "1"
# если вы решили перенести все старые записи с помощью этого скрипта.
$HIDE_FROM_FRIENDS_WALLS = 0 ;
мой $dbh = DBI -> подключить (
"DBI:mysql:database=mysql;host=localhost",
$DB_LOGIN,
$DB_PASS,
) || die "Ошибка подключения к базе данных: $!n" ;
мой $lj = новый LJ :: Simple (<
пользователь => $LJ_NAME,
пройти => $LJ_PASS,
сайт => "livejournal.com:80" ,
> ) ;
(определено $lj)
|| die "$0: Не удалось войти в ЖЖ: $LJ::Simple::errorn" ;
$sql = "выберите ctime, UNIX_TIMESTAMP(ctime), ссылку, сообщение, picture_fb, id из моих постов, где user='$USER' и lj_html_id имеет значение NULL по пределу описания ctime 0,1;" ;
$dbh -> делать ("использовать $DATABASE;");
@row_ary = $dbh -> selectrow_array ($sql);
если ( $row_ary [ 0 ] == "" )
($ctime, $ctime_ts, $link, $message, $picture_fb, $id) = @row_ary;
$сообщение =~ с/н/
/грамм ; # подготовка CR для html
# построение темы из текста поста
$messagelength = длина ($message);
если ($ длина сообщения > 50) <
$i = index($message. "", "");
сделать <
$j = $i;
$i = index($message. "", "", $i + 1);
> пока ($i < 50);
$subject = (длина ($message) > 50)? substr($сообщение, 0, $j). "…": $сообщение;
мой %Entry = ( ) ; $lj -> Новая запись (% запись) || die "$0: Не удалось создать новую запись: $LJ::Simple::errorn" ;
# обрезанная ссылка
$croppedlink = (длина ($link) > 50?
( substr ( $ ссылка , 0 , 50 ). "…" )
:
$ссылка);
# замена маленькой картинки на большую
если ($picture_fb =~ / https://fbcdn/) <
$большая картинка = $picture_fb ;
$largepicture =~ s/_s/_n/g ;
$вход = "". $ обрезанная ссылка . "
» .
$ сообщение .
«» ;
> иначе <
если ($ ссылка пе "" )
<
$ запись = "
» . $ обрезанная ссылка . « $сообщение |
» ;
> еще
<
$entry = "$сообщение$ссылка$picture_fb ">" ;
>
>
$lj -> SetEntry (%Entry, $entry) || die "$0: Не удалось подготовить новый пост — $LJ::Simple::errorn" ;
$lj -> SetSubject(%Entry, $subject);
$lj -> SetDate(%Entry, $ctime_ts);
если ($HIDE_FROM_FRIENDS_WALLS) < $lj ->Setprop_backdate(%Entry, 1); >
мой ($item_id, $anum, $html_id) = $lj -> PostEntry (%Entry);
(определено $item_id)
|| die "$0: Не удалось опубликовать запись в журнале: $LJ::Simple::errorn" ;
$sql = "обновить набор моих сообщений user='$USER', lj_ts=now(), lj_item_id = '$item_id', lj_anum = '$anum', lj_html_id = '" . $html_id . "'где идентификатор #339933">. $идентификатор ;
Показать исходный код lj_change.pl
использовать Data :: Dumper ;
использовать POSIX;
использовать ЖЖ :: Простой ;
использовать Time :: Local ;
использовать ДБИ;
использовать DBD::MySQL;
мой $dbh = DBI -> подключиться (
"DBI:mysql:database=mysql;host=localhost",
$DB_LOGIN,
$DB_PASS,
) || die "Ошибка подключения к базе данных: $!n" ;
$dbh -> do ("использовать $DATABASE;;");
$sql = "выберите lj_item_id из моих сообщений, где lj_html_id не является NULL порядком по ctime desc;" ;
$results = $dbh -> selectall_hashref ($sql, 'lj_item_id');
foreach мой $id ( ключи % $ результаты ) <
#$id2 = $результаты->;
нажать @ids, $id ;
>
мой $lj = новый LJ :: Simple (<
пользователь => $LJ_LOGIN,
пройти => $LJ_PASS,
сайт => undef,
прокси => undef,
> ) ;
(определено $lj)
|| die "$0: Не удалось войти в ЖЖ: $LJ::Simple::errorn" ;
print "вошел в систему. n" ;
для ( @id ) <
print "запрашиваю запись $_.n" ;
( определено $lj -> GetEntries ( % Entries,undef, "one" , $_ )) или вывести "$0: Не удалось получить записи — $LJ::Simple::errorn" ;
мой $item = $Entries <$_>;
если ($operation == 'setbackdate') <
$lj -> Setprop_backdate ( $item, 1 ) или вывести "$0: Не удалось установить свойство даты назад — $LJ::Simple::errorn" ;
$lj -> EditEntry ( $item ) или вывести "$0: Ошибка редактирования записи — $LJ::Simple::errorn" ;
>
если ($operation == 'очистить') <
$lj -> EditEntry ($item, undef);
>
$sql = "обновить набор моих сообщений.";
$dbh -> сделать ($sql);
>
напечатать "готово.n" ;
выход (0);
P.S. К слову сказать, подсветка синтаксиса означает, что хабраредактор работает из рук вон плохо. В первом скрипте подсветка работает до строки с подключением к БД (DBI->connect): стоит ей появиться, как источник тега перераспределить синтаксис Perl-а. Во вторую очередь, вероятно, тоже на ней сбоит. Пришлось делать подсветку через шрифт тега.