📜 ⬆️ ⬇️

From Facebook - to Livejournal, Twitter & Vkontakte, crossposting in source codes and comments

In this article, I will talk about my experience in developing crossposting from my Facebook to my Livejournal (hereinafter referred to as LJ), and also share source texts ready for launching on your accounts.

The reason for writing the scripts was to get the opportunity to search for your records - an opportunity that Facebook could not run as part of its service, as well as the “revitalization” of its LJ. Since access to any posts on Facebook requires mandatory authorization, search engines are obviously not allowed to search engines. Specifically, in my case, this is inconvenient: links, videos and thoughts that I publish on the social network, I often publish "for the future" - and the time often comes when this information becomes necessary, but it is almost impossible to find.

Also, using scripts published here, it was possible to transfer the archive of existing Facebook records : more than 2000 archived messages from my Facebook were transferred to LiveJournal with corresponding dates. That is, if you have not had LJ, you can immediately fill it with information for all time.
')
Also in the article I post ready-made Perl scripts with which you can translate Facebook statuses into Livejournal, and from there, with the appropriate settings, in Vkontakte, Twitter and RSS, and using additional web services - in almost all blog engines.


Preamble


It so happened that I left Facebook on Facebook two years ago. This was due to a bunch of problems in the LiveJournal itself, a retardation with the development of the service, and, as a result, the mass departure of my friends from there to the blue-and-white social network.

At the same time, LJ remains the only open blog site with a flexible post format that does not impose significant technical restrictions on the size of the post or on its design. In LJ still remains a lot of interesting personalities, of which a lot of my friends.

A very big advantage of LJ was its openness to search engines. What I wrote to the blog, I can then find through the “search engines”, and in practice, the Yandex or Google index is updated a few minutes after the post is published. Understanding that your article, note or find can be useful not only to your friends, motivates well enough to write this useful thing again and again. However, the same logic works in relation to Habra.

I learned crossposting services that allow you to automatically post a message to several social networks. Unfortunately, nothing sensible of them was found. Almost everywhere they are forced to write a post in a special interface, or LJ is used as the main place, which is inconvenient for me personally. The most interesting service was the startup IFTTT.com , which allows you to create universal rules of the form “tweeted on twitter - send by sms”, “appeared on facebook - send on twitter”, “it's going to rain - warn by sms”, etc. Yes, it can be adapted for some needs, but he cannot post from Facebook to LiveJournal anyway. There is one big drawback in all cross-posting services - they are too universal and poorly customizable, and even rare ones include popular Russian social networks. The same LiveJournal on the list is extremely rare. And if you can get a post in LiveJournal through RSS, then you can send it there only through the API.

There is one more reason. I want to be able to control what to fast, depending on the content. For example, I can consider it necessary to automatically publish a photo in LJ, and not a link to it. Or transfer to Twitter the entire post, and not a link to it, as many services do. To do this, I should be able to fix the script myself, for my needs.

As a result, the goal was to cover the social networks of Twitter, LiveJournal, Facebook and Vkontakte, leaving Facebook a “launching pad” for the post. Since LJ itself can post on Vkontakte and Twitter, as well as export posts to RSS for integration with Drupal, the following scheme was drawn:



About API


Facebook has, in my opinion, the smartest API. This interface allows you to do anything with the data that you contribute to the social network - get it in a structured form, change or delete it through a lot of convenient mechanisms, FQL, HTTP requests. Against this background, a relatively small number of third-party applications working with Facebook were surprised.

In addition to using the API, I also tried parsing the pages of a simplified mobile version of Facebook — this allowed me to pull out more information than the API gives. In some cases, this is quite a useful mechanism. In this case, it was possible to completely manage with standard features.

In the Perl libraries, everything turned out fine: for Perl on CPAN, there were several modules that implement work with Facebook, but because of the simplicity of the protocol, there is little need for them. The request data is transmitted via the URL, the result is returned in JSON. As for LJ, it has several different APIs, of which the simplest is LJ XML-RPC . I used a ready-made module for Perl that implements a fairly stable work with LJ - LJ :: Simple .

Features of crossposting from Facebook


Access to Facebook via access token, received by an external application for a limited time with limited rights. The fading time of the session and the received access_token is measured from 2 to 25 hours. It is possible to get a long-live token with a lifetime of up to 60 days. Logically, you need to update the access_token after fading each time - after 5 days it will happen or after 60. In the above scripts, automatic updating is not provided, as is the notification that the access token is rotten.

The second feature is that your messages are available on the wall along with messages from other users, and if you don’t think that only yours should be cross-posted, other users may be able to publish something wrong with you on Facebook. By default, on Facebook, all your friends have the opportunity to publish anything to you on the wall, especially the opportunity to receive a cloud of comments from friends to someone else's photo, on which you are “marked”. If you do not include the moderation of someone else's messages on the wall, then with the advent of crossposting, these photos will go beyond the limits of Facebook.

Some Facebook posts are “non-informative” - like some messages sent by applications. Crossposter can filter them and not transfer them to Livejournal, but then everyone adjusts for themselves, of course. Some posts specifically take a different look: for example, a photo is transformed from a link to Facebook into a large photo embedded in a post on LJ.

You must also take into account the need to set the “backdate” flag when posting backdating on LJ. For this there is a special constant at the beginning of the script. The peculiarity lies in the fact that if you publish a post in LiveJournal for March 2011, then it is placed in the friends tape as fresh (albeit with an old date), and when linked up with twitter, it is also published as fresh. If you put a special tick in the interface, or set the backdate property through an API, then it is excluded from the friends ribbon. To transfer an archive, installing backdate is a must, then it can be disabled.

Well, you need to remove the connection with Facebook, otherwise it will turn out to be a closed loop (protection in the script just in case).

What happened


Here I publish scripts developed in the process of research, such proof of concept. They are also adapted for publication as part of the article - for example, the general parameters for connecting to the database are not transferred to separate files, the code is not broken down by functions and files, unimportant ones are removed, etc.

The cross-poster architecture assumes two-stage work: saving intermediate results to the database and exporting records from the database to LiveJournal. In the future, this database can be used as an independent database, and having local data it is easy to add scripts for export to other social networks, RSS.

To save to the database, use the facebook.pl script. Its purpose is to get a page from the wall from Facebook and, in case there is a next page, give it its identifier, if not, give it “all done”. The identifier is a script parameter, so to load the next page you need to call a script with this identifier, etc., until we get all done in response.

Please note that in order for facebook.pl to work, you need to change the settings for connecting to the database, the identifier of your log in LiveJournal, and enter access_token. For debugging, you can create a short-lived access token in the Facebook Graph API Explorer . In order to get access for 60 days, you need to create an application, get AppId and SecretId, then create an access token using the link provided, selecting this application from the drop-down list. Pay attention to the list of rights - the lack of some checkboxes may restrict access to entries on your wall: for example, the external application will no longer show reshares from other users or photos or something else. If you are not afraid to leave extra accesses in the scripts, it is better to put all the checkboxes at all.

For posting on LJ from DB, the update_lj.pl script is used. This article uses its debug version - it takes one vacant post prepared from facebook.pl from the database, sends it to LJ, returns the page identifier to LJ, marks the post as sent. This is an intermediate version, and I leave it here, because in case of any problems, removing posts created by the script from LJ is massively very, very inconvenient.

In case, if after all, LJ has been replenished with a bunch of automatically created erroneous posts, you can perform selective editing, deletion or modification of properties with the lj_change.pl script provided at the end of the post.

As a result, to transfer the archive, you need to go through all the pages with a bash script that calls facebook.pl the necessary number of times, and then run updatelj.pl as many times as you have records in the database. To update facebook.pl regularly, it’s enough to call krone once an hour or once a day, then call updatelj.pl after such a bash-script.

I will be glad to any comments and additions, as well as to those enthusiasts who want or can make an external universal service out of it.

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, //     ? `lj_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, //     `lj_item_id` int(11) DEFAULT NULL, //    () `lj_anum` int(11) DEFAULT NULL, //   ,  item_id  html_id `lj_html_id` int(11) DEFAULT NULL, //    (, ,   .html) `user` varchar(50) DEFAULT NULL, //  (     ) PRIMARY KEY (`id`) ) 


 #!/bin/bash #   NEXT=`./facebook.pl`; echo $NEXT; while [ "$NEXT" != "all done" ] do NEXT=`$NEXT`; echo $NEXT; done 


Show source for facebook.pl
#! / usr / bin / perl

use open qw ( : std : utf8 ) ;
use LWP :: Simple ;
use YAML :: Tiny ;
use JSON ;
use URI ;
use DBI ;
use DBD :: MySQL ;

$ DB_LOGIN = "======== DB-USER ==========" ;
$ DB_PASS = "======== DB-PASS ==========" ;
$ DATABASE = "======== DB-NAME ==========" ;
$ USER = '======== YOUR-LJ-USER ==========' ;
my $ access_token = '======== YOUR-ACCESS-TOKEN-SEE-GRAPH.FACEBOOK.COM-FOR-THE-DETAILS ==========' ;

#Argv [0] CGI parameter
$ until = $ ARGV [ 0 ] ;

my $ dbh = DBI -> connect ( "DBI: mysql: database = mysql; host = localhost" , $ DB_LOGIN , $ DB_PASS ) || die "Error connecting to database: $! n" ;

$ dbh -> do ( "use $ DATABASE;" ) ;

# next four lines are for facebook graph api request
my $ uri = new URI ( 'https://graph.facebook.com/me/feed' ) ;
$ uri -> query_form ( { access_token => $ access_token , until => $ until , base_amount => 1 , value => 1 } ) ;
my $ resp = get ( "$ uri" ) ;
$ resp = defined $ resp ? decode_json ( $ resp ) : undef ;

$ next = $ resp -> { paging } -> { next } ;
$ next = ~ /until=(.+)$/ ;
$ until = $ 1 ;

or the bash command for the next iteration. It looks a bit queer ...

if ( $ until ! = "" ) {
print "./facebook.pl" . $ until . "n" ;
} else
{
print "all done" ;
}

# updating database
for my $ post ( @ { $ resp -> { data } } ) {
$ ctime = $ post -> { created_time } ;
$ ctime = ~ / ( ddddd) - ( ddd ) - ( ddd). ( ddd) : ( ddd ) : ( ddd ) / ;
( $ y , $ m , $ d , $ h , $ i , $ s ) = ( $ 1 , $ 2 , $ 3 , $ 4 , $ 5 , $ 6 ) ;
$ time = "$ h: $ i: $ s" ;
$ sqltime = $ y . $ m . $ d . $ h . $ i . $ s ;
$ message = $ post -> { message } ;
$ link = $ post -> { link } ;
$ picture = $ post -> { picture } ;

$ sql = "select * from myposts where user = '$ USER' and ctime = '$ sqltime'" ;
$ sth = $ dbh -> prepare ( $ sql ) ;
$ sth -> execute ;

if ( $ sth -> rows == 0 ) {

$ sql = "insert into myposts set user = '$ USER', link =" . $ dbh -> quote ( $ link ) . ", picture_fb =" . $ dbh -> quote ( $ picture ) . ", message =" . $ dbh -> quote ( $ message ) . ", ctime = '$ sqltime'" ;

# the following line is intended for filtering twitter-like posts. I’ve been able to follow your Facebook post.
if ( $ message ! ~ / t . co // ) {
$ dbh -> do ( $ sql ) ;
}
}
}


Display source code update_lj
#! / usr / bin / perl

use LJ :: Simple ;
use Date :: Manip ;
use DBI ;
use DBD :: MySQL ;

$ USER = '======== YOUR-LJ-USER ==========' ;
$ DB_LOGIN = "======== DB-USER ==========" ;
$ DB_PASS = "======== DB-PASS ==========" ;
$ LJ_NAME = "======== LJ-USER ==========" ;
$ LJ_PASS = "======== LJ-PASS ==========" ;

$ DATABASE = 'facebook' ;
$ DEBUG = 1 ;

# it's extremely important to set the following constant to "1"
# if you’ve decided to migrate
$ HIDE_FROM_FRIENDS_WALLS = 0 ;

my $ dbh = DBI -> connect (
"DBI: mysql: database = mysql; host = localhost" ,
$ DB_LOGIN ,
$ DB_PASS ,
) || die "Error connecting to database: $! n" ;

my $ lj = new LJ :: Simple ( {
user => $ LJ_NAME ,
pass => $ LJ_PASS ,
site => "livejournal.com:80" ,
} ) ;
( defined $ lj )
|| die "$ 0: Failed to log into LiveJournal: $ LJ :: Simple :: errorn" ;

$ sql = "select ctime, UNIX_TIMESTAMP (ctime), link, message, picture_fb, id from myposts where user = '$ USER' and lj_html_id is NULL order by ctime desc limit 0,1;" ;
$ dbh -> do ( "use $ DATABASE;" ) ;

@row_ary = $ dbh -> selectrow_array ( $ sql ) ;
if ( $ row_ary [ 0 ] == "" ) { exit ; }

( $ ctime , $ ctime_ts , $ link , $ message , $ picture_fb , $ id ) = @row_ary ;

$ message = ~ s / n / <br> <br> / g ; # preparing CRs for html

if ( $ DEBUG ) { print $ message . "n" ; }

# building the subject from the text of the post
$ messagelength = length ( $ message ) ;
if ( $ messagelength > 50 ) {
$ i = index ( $ message . "" , "" ) ;
do {
$ j = $ i ;
$ i = index ( $ message . "" , "" , $ i + 1 ) ;
} while ( $ i < 50 ) ;
$ subject = ( length ( $ message ) > 50 ) ? substr ( $ message , 0 , $ j ) . "..." : $ message ;

if ( $ DEBUG ) { print "posting $ subject ... n" ; }

my % Entry = ( ) ; $ lj -> NewEntry ( % Entry ) || die "$ 0: Failed to create new entry: $ LJ :: Simple :: errorn" ;

# croppedlink
$ croppedlink = ( length ( $ link ) > 50 ?
( substr ( $ link , 0 , 50 ) . "..." )
:
$ link ) ;
# replacing the big picture
if ( $ picture_fb = ~ / https : // fbcdn / ) {
$ largepicture = $ picture_fb ;
$ largepicture = ~ s / _s / _n / g ;
$ entry = "<a href='$link'>" . $ croppedlink . "</a> <br>" .
$ message .
"<br> <img src = '$ largepicture'>" ;
} else {
if ( $ link ne "" )
{
$ entry = "<table> <tr> <td> <img src =" ". $ picture_fb." "align = left> </ td> <td> <a href = '" . $ link . "'>" .
$ croppedlink .
"</a> <br> $ message <br clear=all> </ td> </ tr> </ table>" ;
} else
{
$ entry = "$ messagenn <a href='$link'> $ link </a> <br> <img src =" $ picture_fb ">" ;
}
}
$ lj -> SetEntry ( % Entry , $ entry ) || die "$ 0: Failed to prepare new post - $ LJ :: Simple :: errorn" ;
$ lj -> SetSubject ( % Entry , $ subject ) ;
$ lj -> SetDate ( % Entry , $ ctime_ts ) ;
if ( $ HIDE_FROM_FRIENDS_WALLS ) { $ lj -> Setprop_backdate ( % Entry , 1 ) ; }
my ( $ item_id , $ anum , $ html_id ) = $ lj -> PostEntry ( % Entry ) ;
( defined $ item_id )
|| die "$ 0: Failed to post journal entry: $ LJ :: Simple :: errorn" ;

if ( $ DEBUG ) { print "created item_id:" . $ item_id . ", anum:" . $ anum . ", html_id:" . $ html_id . "n" ; }

$ sql = "update myposts set user = '$ USER', lj_ts = now (), lj_item_id = '$ item_id', lj_anum = '$ anum', lj_html_id = '" . $ html_id . "'where id =" . $ id ;

$ dbh -> do ( $ sql ) ;


Show source for lj_change.pl
! / usr / bin / perl

use Data :: Dumper ;
use POSIX ;
use LJ :: Simple ;
use Time :: Local ;
use DBI ;
use DBD :: MySQL ;

$ LJ_LOGIN = "=========== LJ-LOGIN ============" ;
$ LJ_PASS = "=========== LJ-PASS ============" ;
$ DB_LOGIN = "=========== DB-LOGIN ============" ;
$ DB_PASS = "=========== DB-PASS ============" ;
$ DATABASE = "=========== DATABASE ============" ;
$ operation = "..." ; #setbackdate, purge ...

my $ dbh = DBI - > connect (
"DBI: mysql: database = mysql; host = localhost" ,
$ DB_LOGIN,
$ DB_PASS,
) || die "Error connecting to database: $! n" ;

$ dbh - > do ( "use $ DATABASE ;;" ) ;
$ sql = "select lj_item_id from myposts where lj_html_id is not NULL order by ctime desc;" ;

$ results = $ dbh - > selectall_hashref ( $ sql, 'lj_item_id' ) ;
foreach my $ id ( keys % $ results ) {
# $ id2 = $ results -> {lj_item_id};
push @ids, $ id ;
}
my $ lj = new LJ :: Simple ( {
user => $ LJ_LOGIN,
pass => $ LJ_PASS,
site => undef,
proxy => undef,
} ) ;

( defined $ lj )
|| die "$ 0: Failed to log into LiveJournal: $ LJ :: Simple :: errorn" ;

print "logged on ... n" ;

my % Entries = ( ) ;

for ( @ids ) {
print "requesting entry $ _... n" ;

( defined $ lj - > GetEntries ( % Entries, undef, "one" , $ _ ) ) or print "$ 0: Failed to get entries - $ LJ :: Simple :: errorn" ;
my $ item = $ Entries { $ _ } ;

if ( $ operation == 'setbackdate' ) {
$ lj - > Setprop_backdate ( $ item, 1 ) or print "$ 0: Failed to set back date property - $ LJ :: Simple :: errorn" ;
$ lj - > EditEntry ( $ item ) or print "$ 0: Failed to edit entry - $ LJ :: Simple :: errorn" ;
}
if ( $ operation == 'purge' ) {
$ lj - > EditEntry ( $ item, undef ) ;
}
$ sql = "update myposts set ..." ;
$ dbh - > do ( $ sql ) ;
}
print "done.n" ;
exit ( 0 ) ;



PS By the way, syntax highlighting by means of a habra editor does not work very well. In the first script, the backlight works up to the database connection string (DBI-> connect): as soon as it appears, the source tag stops distinguishing Perl syntax. In the second, it probably also fails. I had to make the backlight through the font tag.

Source: https://habr.com/ru/post/149093/


All Articles