LJ::Simple - A perl module to access LiveJournal via its flat protocol
LJ::Simple is an object based perl module which is used to access
LiveJournal-based web logs. This module implements most of the
flat protocol LiveJournal uses; for details of this protocol please
see: http://www.livejournal.com/developer/protocol.bml
This module requires nothing other than the modules which come with the
standard perl 5.6.1 distribution. The only modules it requires are
POSIX and Socket.
If you have the Digest::MD5 module available then the code will make use of
encrypted passwords automatically. However Digest::MD5 is not required for
this module to work.
LJ::Simple is a trival API to access LiveJournal. Currently it
allows you to:
The following simple examples shows you how to use the module to post a simple LiveJournal entry.
LJ::Simple::QuickPost() is a routine which allows you to quickly post an entry into
LiveJournal; as such it lacks a lot of the abilities which using the object-based
interface provides. The LJ::Simple::QuickPost() routine is explained in depth below, however
the following example shows how it can be used to easily post to LiveJournal:
use LJ::Simple;
LJ::Simple::QuickPost(
user => "test",
pass => "test",
entry => "Just a simple entry",
) || die "$0: Failed to post entry: $LJ::Simple::error\n";
use LJ::Simple;
# Log into the server
my $lj = new LJ::Simple ({
user => "test",
pass => "test",
site => undef,
});
(defined $lj)
|| die "$0: Failed to log into LiveJournal: $LJ::Simple::error\n";
# Prepare the event
my %Event=();
$lj->NewEntry(\%Event) ||
die "$0: Failed to create new entry: $LJ::Simple::error\n";
# Put in the entry
my $entry=<<EOF;
A simple entry made using <tt>LJ::Simple</tt> version $LJ::Simple::VERSION
EOF
$lj->SetEntry(\%Event,$entry)
|| die "$0: Failed to set entry: $LJ::Simple::error\n";
# Say we are happy
$lj->SetMood(\%Event,"happy")
|| die "$0: Failed to set mood: $LJ::Simple::error\n";
# Post the event
my ($item_id,$anum,$html_id)=$lj->PostEntry(\%Event);
(defined $item_id)
|| die "$0: Failed to post journal entry: $LJ::Simple::error\n";
There are various variables which can be used to control certain aspects of the module. It is generally recommended that if you wish to change these variables that you do so before you create the initial object.
The variable you are most likely to use is $LJ::Simple::error
which holds error messages if any of the LJ::Simple calls
fail.
CODE: An error in the code calling the API INTERNAL: An internal error in this module
1, debugging messages are sent to stderr.
1 the protocol used to talk to the remote server is sent to stderr.
1 the raw protocol used to talk to the remote server is sent to stderr;
this is only useful if you are doing debugging on LJ::Simple itself as the protocol
is shown as the module gets it from the server; non-printable characters are converted
to their octal presentation form, ie a newline becomes \012.
It should be noted that if $LJ::Simple::raw_protocol is set along with
$LJ::Simple::protocol then the raw protocol display takes precedence for data
returning from the LJ server.
1 the LiveJournal server is told to expect UTF-8 encoded characters.
If you enable this the module will attempt to use the utf8 perl module.
The default is see if we have a version of Perl with UTF-8 support and use it if its available.
1 we make use of the challenge-response system instead of using
plain or hashed passwords. This does add some overhead into processing requests
since every action has to be preceeded by a request for a challenge value from
the server.
The default is to see if we have the Digest::MD5 module available and if
so we make use of the challenge-response system. This can be disabled by
setting the variable to 0.
undef the API will block until data is avaiable.
undef. When given a reference to a sub-routine this
module will call the given sub-routine at various stages of processing the responses
to the LiveJournal server. This is intended for GUI applications which need to process
event queues, update progress bars, etc. When called the sub-routine is passed a
number of variables which maybe useful; the calling method is:
&{sub}($mode,$status,$action,$bytes_in,$bytes_out,$time,$waiting)
$mode - The mode sent to the LJ server
$status - The status of the request; ranges from 0 to 1
$action - The action performed
$bytes_in - The number of bytes read from the remote server
$bytes_out - The number of bytes written to the remote server
$time - The time taken so far in seconds
$waiting - Are we waiting for a response from the server ?
It should be noted that if $waiting is set to 1 then it is highly recommended
that the sub-routine calls select() itself to provide at least some time delay. If
this is not done it is likely that this module will consume far more CPU than necessary.
An example sub-routine follows:
sub LJStatus {
my ($mode,$status,$action,$bytes_in,$bytes_out,$time,$waiting) = @_;
print "\$mode = $mode\n";
print "\$status = $status\n";
print "\$action = $action\n";
print "\$bytes_in = $bytes_in\n";
print "\$bytes_out = $bytes_out\n";
print "\$time = $time\n";
print "\$waiting = $waiting\n";
print "\n";
($waiting) && select(undef,undef,undef,0.5);
}
$LJ::Simple::NonBlock=\&LJStatus;
$LJ::Simple::protocol is set to 1. The sub-routine called must
take two variables; it is called in the following way:
&{sub}($direction,$data,$server,$ip_addr)
$direction - The direction of the flow; 0 means from client to server
and 1 means from server to client
$data - The data which has flowed; there should not be any newlines
with the data, but do not rely on this.
$server - The name of the LJ server we are talking to
$ip_addr - The IP address of the LJ server we are talking to
If both variables are undef then data is about to flow. If just $direction is
undef then $data holds an informational message.
The standard sub-routine which is called is:
sub DefaultProtoSub {
my ($direct,$data,$server,$ip_addr)=@_;
my $arrow="--> ";
if (!defined $direct) {
if (!defined $data) {
print STDERR "Connecting to $server [$ip_addr]\n";
print STDERR "Lines starting with \"-->\" is data SENT to the server\n";
print STDERR "Lines starting with \"<--\" is data RECEIVED from the server\n";
return;
}
$arrow="";
} else {
($direct) && ($arrow="<-- ");
}
print STDERR "$arrow$data\n";
}
$LJ::Simple::ProtoSub=\&DefaultProtoSub;
sysread() call.
LJ::Simple::QuickPost() is a routine which allows you to quick post to LiveJournal.
However it does this by hiding a lot of the details involved in using
LJ::Simple to do this. This routine will do all of the work involved in
logging into the LiveJournal server, preparing the entry and then posting it.
If at any stage there is a failure then 0 is returned and $LJ::Simple::error
will contain the reason why. If the entry was successfully posted to the LiveJournal
server then the routine will return 1.
There are a number of options to the LJ::Simple::QuickPost() routine:
LJ::Simple::QuickPost(
user => Username
pass => Password
entry => Contents of the entry
subject => Subject line of the entry
mood => Current mood
music => Current music
html => HTML content ?
protect => Security settings of the entry
groups => Friends groups list
tags => Tags list
results => Hash to store results in
);
Of these, only the user, pass and entry options are required; all of the other
options are optional. The option names are all case insensitive.
user;
this option is required.
SetMood() method
for processing.
1 or 0. If you want to say that the entry
contains HTML and thus should be considered to be preformatted then set html to
1. Otherwise you can either set it to 0 or not give the option.
protect option. This
option should be given the protection level required for the post and can be one of
the following:
public - The entry is public friends - Entry is friends-only groups - Entry is restricted to friends groups private - Entry is restricted to the journal's owner
If you set the protect option to groups you must also include the groups
option - see below for details.
protect option is set to groups then this option should contain a
list reference which contains the list of groups the entry should be restricted to.
This option is required if the protect option is set to groups.
The keys in the hash point to:
ok - Return code of QuickPost item_id - Item_id as returned by the LiveJournal server anum - Anum as returned by the LiveJournal server html_id - The item_id of the entry as used in HTML url - A URL which could be used to access the entry
It should be noted that when QuickPost() fails, ok will point to
a value of 0 and all other entries in the hash will be undef.
Example code:
# Simple test post
LJ::Simple::QuickPost(
user => "test",
pass => "test",
entry => "Just a simple entry",
) || die "$0: Failed to post entry: $LJ::Simple::error\n";
# A friends-only preformatted entry
LJ::Simple::QuickPost(
user => "test",
pass => "test",
entry => "<p>Friends-only, preformatted, entry</p>",
html => 1,
protect => "friends",
) || die "$0: Failed to post entry: $LJ::Simple::error\n";
# A entry restricted to several friends groups
LJ::Simple::QuickPost(
user => "test",
pass => "test",
entry => "Entry limited to friends groups",
protect => "groups",
groups => [qw( one_group another_group )],
) || die "$0: Failed to post entry: $LJ::Simple::error\n";
# Simple test post with tags and returning HTML
my %Results=();
LJ::Simple::QuickPost(
user => "test",
pass => "test",
entry => "Just a simple entry",
tags => [ "Just a test", "Testing" ],
results => \%Results,
) || die "$0: Failed to post entry: $LJ::Simple::error\n";
print "URL = $Results{url}\n";
## Simplest logon method
my $lj = new LJ::Simple ( {
user => "username",
pass => "password",
} );
## Login with options
my $lj = new LJ::Simple ( {
user => "username",
pass => "password",
site => "hostname[:port]",
proxy => "hostname[:port]",
moods => 0 | 1,
pics => 0 | 1,
fast => 0 | 1,
} );
## Login by using login()
my $lj = LJ::Simple->login ( {
user => "username",
pass => "password",
site => "hostname[:port]",
proxy => "hostname[:port]",
moods => 0 | 1,
pics => 0 | 1,
fast => 0 | 1,
} );
Where:
user is the username to use
pass is the password associated with the username
site is the remote site to use
proxy is the HTTP proxy site to use; see below.
moods is set to 0 if we do not want to download the mood
list. Defaults to 1
pics is set to 0 if we do not want to download the user
picture information. Defaults to 1
fast is set to 1 if we want to perform a fast login.
Default is 0. See below for details of this.
Sites defined in site or proxy are a hostname with an
optional port number, separated by a :, i.e.:
www.livejournal.com www.livejournal.com:80
If site is given undef then the code assumes that you wish to
connect to www.livejournal.com:80. If no port is given then port
80 is the default.
If proxy is given undef then the code will go directly to the
$site unless a suitable environment variable is set.
If no port is given then port 3128 is the default.
LJ::Simple also supports the use the environment variables http_proxy
and HTTP_PROXY to store the HTTP proxy server details. The format of these
environment variables is assumed to be:
http://server[:port]/
Where server is the name of the proxy server and the optional port the
proxy server is on - port 3128 is used if no port is explicitly given.
It should be noted that the proxy environment variables are only checked
if the proxy value is NOT given to the LJ::Simple object creation.
Thus to disable looking at the proxy environment variables use
proxy=>undef in new() or login().
If moods is set to 0 then the mood list will not be pulled from
the LiveJournal server and the following functions will be affected:
o moods() will always return undef (error)
o Setprop_current_mood_id() will not validate the mood_id
given to it.
o SetMood() will not attempt to convert the string it is
given into a given mood_id
If pics is set to 0 then the data on the user pictures will
not be pulled from the LiveJournal server and the following
functions will be affected:
o pictures() will always return undef (error)
o Setprop_picture_keyword() will blindly set the picture keyword
you give it - no validation will be performed.
o DefaultPicURL() will always return undef (error)
If fast is set to 1 then we will perform a fast login. Essentially
all this does is to set up the various entries in the object hash which
the routines called after login expect to see; at no time does it talk to
the LiveJournal servers. What this means is that it is very fast. However it
also means that when you use parts of the API which do talk to the LiveJournal
servers its quite possible that you will get back errors associated with
authentication errors, network outages, etc. In other words, in fast mode
the login will always succeed, no matter what the state the LiveJournal
server we're talking is in. It should be noted that the following functions
will be affected if you enable the fast login:
o moods() will always return undef (error)
o Setprop_current_mood_id() will not validate the mood_id
given to it
o SetMood() will not attempt to convert the string it is
given into a given mood_id
o pictures() will always return undef (error)
o Setprop_picture_keyword() will blindly set the picture keyword
you give it - no validation will be performed
o communities() will always return an empty list
o MemberOf() will always return 0 (error)
o UseJournal() will not validate the shared journal name you
give it
o groups() will always return undef (error)
o MapGroupToId() will always undef (error)
o MapIdToGroup() will always undef (error)
o SetProtectGroups() will always 0 (error)
o message() will always return undef (error)
o The key of "groups" in the list of hashes returned by
GetFriends() will always point to an empty list
o CheckFriends() will return undef (error) if you give it a
list of groups
On success this sub-routine returns an LJ::Simple object. On
failure it returns undef with the reason for the failure being
placed in $LJ::Simple::error.
Example code:
## Simple example, going direct to www.livejournal.com:80
my $lj = new LJ::Simple ({ user => "someuser", pass => "somepass" });
(defined $lj) ||
die "$0: Failed to access LiveJournal - $LJ::Simple::error\n";
## More complex example, going via a proxy server on port 3000 to a
## a LiveJournal system available on port 8080 on the machine
## www.somesite.com.
my $lj = new LJ::Simple ({
user => "someuser",
pass => "somepass",
site => "www.somesite.com:8080",
proxy => "proxy.internal:3000",
});
(defined $lj) ||
die "$0: Failed to access LiveJournal - $LJ::Simple::error\n";
## Another complex example, this time saying that we do not want
## the mood list or user pictures downloaded
my $lj = new LJ::Simple ({
user => "someuser",
pass => "somepass",
pics => 0,
moods => 0,
});
(defined $lj) ||
die "$0: Failed to access LiveJournal - $LJ::Simple::error\n";
## Final example - this one shows the use of the fast logon
my $lj = new LJ::Simple ({
user => "someuser",
pass => "somepass",
fast => 1,
});
(defined $lj) ||
die "$0: Failed to access LiveJournal - $LJ::Simple::error\n";
message()undef if no message is set.
Example code:
my $msg = $lj->message();
(defined $msg) &&
print "LJ Message: $msg\n";
moods($hash_ref)undef on error.
Note that if the LiveJournal
object was created with either moods set to 0 or
with fast set to 1 then this function will always return
an error.
The hash the given reference is pointed to is emptied before it is used and after a successful call the hash given will contain:
%hash = (
list => [ list of mood names, alphabetical ]
moods => {
mood_name => mood_id
}
idents => {
mood_id => mood_name
}
)
Example code:
my %Moods=();
if (!defined $lj->moods(\%Moods)) {
die "$0: LJ error - $LJ::Simple::error";
}
foreach (@{$Moods{list}}) {
print "$_ -> $Moods{moods}->{$_}\n";
}
communities()Example code:
my @communities = $lj->communities();
print join("\n",@communities),"\n";
MemberOf($community)1 if the user is a member of the named community. Returns
0 otherwise.
Example code:
if ($lj->MemberOf("some_community")) {
:
:
:
}
groups($hash_ref)undef on error.
The hash the given reference points to is emptied before it is used and after a successful call the hash given will contain the following:
%hash = (
"name" => {
"Group name" => {
id => "Number of the group",
sort => "Sort order",
name => "Group name (copy of key)",
},
},
"id" => {
"Id" => "Group name",
},
);
Example code:
my %Groups=();
if (!defined $lj->groups(\%Groups)) {
die "$0: LJ error - $LJ::Simple::error";
}
my ($id,$name)=(undef,undef);
while(($id,$name)=each %{$Groups{id}}) {
my $srt=$Groups{name}->{$name}->{sort};
print "$id\t=> $name [$srt]\n";
}
MapGroupToId($group_name)undef and sets
$LJ::Simple::error.
MapIdToGroup($id)undef and sets
$LJ::Simple::error.
pictures($hash_ref)undef on error. Note that
the user has to have defined picture keywords for this to work.
Note that if the LiveJournal
object was created with either pics set to 0 or
with fast set to 1 then this function will always return
an error.
The hash the given reference points to is emptied before it is used and after a successful call the hash given will contain the following:
%hash = (
"keywords" => "URL of picture",
);
Example code:
my %pictures=();
if (!defined $lj->pictures(\%pictures)) {
die "$0: LJ error - $LJ::Simple::error";
}
my ($keywords,$url)=(undef,undef);
while(($keywords,$url)=each %pictures) {
print "\"$keywords\"\t=> $url\n";
}
DefaultPicURL()Note that if the LiveJournal
object was created with either pics set to 0 or
with fast set to 1 then this function will always return
an error.
Example code:
print $lj->DefaultPicURL(),"\n";
user()Example code:
my $user = $lj->user();
fastserver()1 if the user can use the fast servers, 0
otherwise.
Example code:
if ($lj->fastserver()) {
print STDERR "Using fast server for ",$lj->user(),"\n";
}
GetTags()The list of tags is a list of hash references which contain data about the tag; each hash referenced will contain the following:
{
name => The name of the tag
uses => Number of times has the tag been used in total
security => Visibility of the tag; this can be "public", "private",
"friends" or "group"
display => If defined this indicates that the tag is visible to
the S2 style system. If set to undef the tag is usable,
just not exposed to S2
}
The list of tags is returned ordered by the tag names.
Example code:
# Print out the names of the tags
my ($count,@Tags)=$lj->GetTags();
(defined $count) || die "$0: Failed to get list of tags - $LJ::Simple::error\n";
print "Total tags: $count\n";
map { print "$_->{name}\n"; } (@Tags);
GetFriendOf()The list of friends is a list of hash references which contain data about the users who list the current user as a friend. Each hash referenced will contain the following:
{
user => The LiveJournal username
name => The full name of the user
fg => The foreground colour which represents the user
bg => The background colour which represents the user
status => The status of the user
type => The type of the user
}
Both the bg and fg values are stored in the format of ``#RRGGBB''
where the RR, GG, BB values are given as two digit hexadecimal numbers which
range from 00 to ff.
The status of a user can be one of active, deleted, suspended or purged.
The type of a user can either be user which means that the user is a normal
LiveJournal user or it can be community which means that the user is actually a
community which the current LJ user is a member of.
It should be noted that any of the values in the hash above can be undefined if that value was not returned from the LiveJournal server.
The returned list is ordered by the LiveJournal login names of the users.
Example code:
my ($num_friends_of,@FriendOf)=$lj->GetFriendOf();
(defined $num_friends_of) ||
die "$0: Failed to get friends of user - $LJ::Simple::error\n";
print "LJ login\tReal name\tfg\tbg\tStatus\tType\n";
foreach (@FriendOf) {
print "$_->{user}\t",
"$_->{name}\t",
"$_->{fg}\t",
"$_->{bg}\t",
"$_->{status}\t",
"$_->{type}\n";
}
GetFriends()The list of friends is a list of hash references which contain data about the users who list the current user as a friend. Each hash referenced will contain the following:
{
user => The LiveJournal username
name => The full name of the user
fg => The foreground colour which represents the user
bg => The background colour which represents the user
dob => The date of birth for the user
birthday => The birthday of the user
groups => The list of friends groups this user is in
groupmask => The actual group mask for this user
status => The status of the user
type => The type of the user
}
Both the bg and fg values are stored in the format of ``#RRGGBB''
where the RR, GG, BB values are given as two digit hexadecimal numbers which
range from 00 to ff.
The dob value is stored as a Unix timestamp; that is seconds since epoch. If the
user has no date of birth defined or they have only given their birthday then this
value will be undef.
The birthday value is the date of the user's next birthday given as a Unix timestamp.
The groups value is a reference to a list of the friends group this user is a member
of. It should be noted that to have any items in the list the user must be a
member of a friends group and the login() method must not have been called
with the fast login option.
The groupmask value is the actual group mask for the user. This is used to build
the groups list. It is a 32-bit number where each bit represents membership of a
given friends group. Bits 0 and 31 are reserved; all other bits can be used. The bit
a group corresponds to is taken by bit-shifting 1 by the group id number.
The status of a user can be one of active, deleted, suspended or purged.
The type of a user can either be user which means that the user is a normal
LiveJournal user or it can be community which means that the user is actually a
community which the current LJ user is a member of.
It should be noted that any of the values in the hash above can be undefined if that value was not returned from the LiveJournal server.
The returned list is ordered by the LiveJournal login names of the users.
Example code:
use POSIX;
my ($num_friends,@Friends)=$lj->GetFriends();
(defined $num_friends) ||
die "$0: Failed to get friends - $LJ::Simple::error\n";
my $f=undef;
foreach $f (@Friends) {
foreach (qw(dob birthday)) {
(defined $f->{$_}) || next;
$f->{$_}=strftime("%Y/%m/%d",localtime($f->{$_}));
}
my ($k,$v)=(undef,undef);
while(($k,$v)=each %{$f}) {
(!defined $v) && ($f->{$k}="[undefined]");
}
print "$f->{user}\n";
print " Name : $f->{name}\n";
print " Colors : fg->$f->{fg} bg->$f->{bg}\n";
print " DOB : $f->{dob}\n";
print " Next birthday: $f->{birthday}\n";
print " Status : $f->{status}\n";
print " Type : $f->{type}\n";
if ($#{$f->{groups}}>-1) {
print " Friend groups:\n";
print " + ",join("\n + ",@{$f->{groups}}),"\n";
} else {
print " Friend groups: [none]\n";
}
print "\n";
}
CheckFriends(@groups)1 if there has been an update
to your friends list and 0 if not. The second item in the list holds the number
of seconds you must wait before calling CheckFriends() again.
In the event of an error undef is returned in the first item of the list.
The routine can be given an optional list of friends group to check instead of just looking at all of the friends for the user.
Example code:
while(1) {
my ($new_friends,$next_update)=$lj->CheckFriends();
(defined $new_friends) ||
die "$0: Failed to check friends - $LJ::Simple::error\n";
($new_friends) && print "Friends list updated\n";
sleep($next_update+1);
}
GetDayCounts($hash_ref,$journal)undef is returned.
There is an optional argument - $journal - which can be used to gather this
data for a shared journal the user has access to. If not required then this
value should be undef or an empty string.
The key to the hash is a date, given as seconds since epoch (i.e. time_t)
and the value is the number of entries made on that day. Only dates which have
journal entries made against them will have values in the hash; thus it can be
assumed that if a date is not in the hash then no journal entries were made
on that day.
The hash will be emptied before use.
Example code:
use POSIX;
(defined $lj->GetDayCounts(\%gdc_hr,undef))
|| die "$0: Failed to get day counts - $LJ::Simple::error\n";
foreach (sort {$a<=>$b} keys %gdc_hr) {
printf("%s %03d\n",strftime("%Y/%m/%d",localtime($_)),$gdc_hr{$_});
}
GetFriendGroups($hash_ref)undef is returned.
The hash key is the id number of the friends group as it is possible to have multiple friends groups with the same name. Each hash value is a hash reference which points to the following hash:
{
id => Id of the group; used to create permission masks
name => Name of the group
sort => Sort order number from 0 to 255
public => Public group ? 1 for yes, 0 for no
}
The hash given will be emptied before use.
Example code:
my %fg=();
(defined $lj->GetFriendGroups(\%fg)) ||
die "$0: Failed to get groups - $LJ::Simple::error\n";
my $format="| %-4s | %-2s | %-6s | %-40s |\n"; my $line=sprintf($format,"","","",""); $line=~s/\|/+/go; $line=~s/ /-/go; print $line; printf($format,"Sort","Id","Public","Group"); print $line;
foreach (sort {$fg{$a}->{sort}<=>$fg{$b}->{sort}} keys %fg) {
my $hr=$fg{$_};
my $pub="No";
$hr->{public} && ($pub="Yes");
printf($format,$hr->{sort},$hr->{id},$pub,$hr->{name});
}
print $line;
In case you're wondering, the above code outputs something similar to the following:
+------+----+--------+------------------------------------------+ | Sort | Id | Public | Group | +------+----+--------+------------------------------------------+ | 5 | 1 | Yes | Good Friends | | 10 | 2 | No | Communities | +------+----+--------+------------------------------------------+
NewEntry($event)On success returns 1, on failure returns 0
Example code:
my %Entry=();
$lj->NewEntry(\%Entry)
|| die "$0: Failed to prepare new post - $LJ::Simple::error\n";
SetDate($event,$time_t)time_t (i.e. seconds
since epoch) value. Bare in mind that you may need to call
$lj->Setprop_backdate(\%Event,1) to backdate the journal entry if the journal being
posted to has events more recent than the date being set here. Returns 1 on
success, 0 on failure.
If the value given for time_t is undef then the current time is used.
If the value given for time_t is negative then it is taken to be relative
to the current time, i.e. a value of -3600 is an hour earlier than the
current time.
Note that localtime() is called to convert the time_t value into
the year, month, day, hours and minute values required by LiveJournal.
Thus the time given to LiveJournal will be the local time as shown on
the machine the code is running on.
Example code:
## Set date to current time
$lj->SetDate(\%Event,undef)
|| die "$0: Failed to set date of entry - $LJ::Simple::error\n";
## Set date to Wed Aug 14 11:56:42 2002 GMT
$lj->SetDate(\%Event,1029326202)
|| die "$0: Failed to set date of entry - $LJ::Simple::error\n";
## Set date to an hour ago
$lj->SetDate(\%Event,-3600)
|| die "$0: Failed to set date of entry - $LJ::Simple::error\n";
SetMood($event,$mood)$lj->Setprop_current_mood() and $lj->Setprop_current_mood_id(\%Event,)
routines, this routine will attempt to first attempt to find the mood given
to it in the mood list returned by the LiveJournal server. If it is unable to
find a suitable mood then it uses the text given.
Note that if the LiveJournal
object was created with either moods set to 0 or
with fast set to 1 then this function will not attempt to find the
mood name given in $mood in the mood list.
Returns 1 on success, 0 otherwise.
Example code:
$lj->SetMood(\%Event,"happy")
|| die "$0: Failed to set mood - $LJ::Simple::error\n";
UseJournal($event,$journal)Returns 1 on success, 0 otherwise.
Example code:
$lj->UseJournal(\%Event,"some_community")
|| die "$0: Failed to - $LJ::Simple::error\n";
SetSubject($event,$subject)o Limited to a length of 255 characters o No newlines are allowed
Returns 1 on success, 0 otherwise.
Example code:
$lj->SetSubject(\%Event,"Some subject")
|| die "$0: Failed to set subject - $LJ::Simple::error\n";
SetEntry($event,@entry)join()ed together with a newline between each
list entry.
If the list is null or undef then any existing entry is removed.
Returns 1 on success, 0 otherwise.
Example code:
# Single line entry
$lj->SetEntry(\%Event,"Just a simple entry")
|| die "$0: Failed to set entry - $LJ::Simple::error\n";
# Three lines of text
my @stuff=(
"Line 1",
"Line 2",
"Line 3",
);
$lj->SetEntry(\%Event,@stuff)
|| die "$0: Failed to set entry - $LJ::Simple::error\n";
# Clear the entry
$lj->SetEntry(\%Event,undef)
|| die "$0: Failed to set entry - $LJ::Simple::error\n";
$lj->SetEntry(\%Event)
|| die "$0: Failed to set entry - $LJ::Simple::error\n";
AddToEntry($event,@entry)$lj->SetEntry() the list given to
this routine will be join()ed together with a newline between each
list entry.
If $lj->SetEntry() has not been called then $lj->AddToEntry() acts
in the same way as $lj->SetEntry().
If $lj->SetEntry() has already been called then calling $lj->AddToEntry()
with a null list or a list which starts with undef is a NOP.
Returns 1 on success, 0 otherwise.
Example code:
# Single line entry
$lj->AddToEntry(\%Event,"Some more text")
|| die "$0: Failed to set entry - $LJ::Simple::error\n";
# Three lines of text
my @stuff=(
"Line 5",
"Line 6",
"Line 7",
);
$lj->AddToEntry(\%Event,@stuff)
|| die "$0: Failed to set entry - $LJ::Simple::error\n";
SetProtect($event,$type,@args)SetProtect*() routines
for the caller. This takes two or more arguments; the first argument is
the hash reference of the current event. The second argument is the
type of security we are setting. Subsequent arguments are related to
the security type. Available types and their arguments are:
+---------+------------------+------------------------------------+ | Type | Additional args | Security | +---------+------------------+------------------------------------+ | public | None | Public - the default | | friends | None | Friends only | | groups | A list of groups | Restricted to groups of friends | | private | None | Private - only the user can access | +---------+------------------+------------------------------------+
On success this routine returns 1; otherwise it returns 0 and
sets $LJ::Simple::error to the reason why.
Example code:
## Make entry public (the default)
$lj->SetProtect(\%Event,"public")
|| die "$0: Failed to make entry public - $LJ::Simple::error\n";
## Make entry friends only
$lj->SetProtect(\%Event,"friends")
|| die "$0: Failed to make entry friends only - $LJ::Simple::error\n";
## Make entry only readable by friends in the groups "close" and "others"
$lj->SetProtect(\%Event,"groups","close","others")
|| die "$0: Failed to make entry public - $LJ::Simple::error\n";
## Make entry private so only the journal owner can view it
$lj->SetProtect(\%Event,"private")
|| die "$0: Failed to make entry private - $LJ::Simple::error\n";
SetProtectPublic($event)LJ::Simple - this method is most
useful when working with an existing post. Returns 1 on success, 0
otherwise.
Example code:
$lj->SetProtectPublic(\%Event)
|| die "$0: Failed to make entry public - $LJ::Simple::error\n";
SetProtectFriends($event)1 on success, 0 otherwise.
Example code:
$lj->SetProtectFriends(\%Event)
|| die "$0: Failed to protect via friends - $LJ::Simple::error\n";
1 on success, 0 otherwise.
Example code:
$lj->SetProtectGroups(\%Event,"foo","bar")
|| die "$0: Failed to protect via group - $LJ::Simple::error\n";
SetProtectPrivate($event)1 on success, 0 otherwise.
Example code:
$lj->SetProtectPrivate(\%Event)
|| die "$0: Failed to protect via private - $LJ::Simple::error\n";
Setprop_taglist($event,@tags)@tags is a list of the tags to give the
entry.
Example code:
$lj->Setprop_taglist(\%Event,qw( gabe pets whatever )) ||
die "$0: Failed to set back date property - $LJ::Simple::error\n";
Setprop_backdate($event,$onoff)$onoff
value takes either 1 for switching the property on or 0 for switching the
property off. Returns 1 on success, 0 on failure.
You will need to set this value if the journal entry you are sending has a date earlier than other entries in your journal.
Example code:
$lj->Setprop_backdate(\%Event,1) ||
die "$0: Failed to set back date property - $LJ::Simple::error\n";
Setprop_current_mood($event,$mood)It is better to use $lj->SetMood() as that will automatically use a
mood known to the LiveJournal server if it can.
Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_current_mood(\%Event,"Happy, but tired") ||
die "$0: Failed to set current_mood property - $LJ::Simple::error\n";
Setprop_current_mood_id($event,$id)Note that if the LiveJournal
object was created with either moods set to 0 or
with fast set to 1 then this function will not attempt to validate
the mood_id given to it.
It is better to use $lj->SetMood() as that will automatically use a
mood known to the LiveJournal server if it can.
Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_current_mood_id(\%Event,15) ||
die "$0: Failed to set current_mood_id property - $LJ::Simple::error\n";
Setprop_current_music($event,$music)Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_current_music(\%Event,"Collected euphoric dance") ||
die "$0: Failed to set current_music property - $LJ::Simple::error\n";
Setprop_preformatted($event,$onoff)1 for true and 0 for false.
Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_preformatted(\%Event,1) ||
die "$0: Failed to set property - $LJ::Simple::error\n";
Setprop_nocomments($event,$onoff)1 for true and 0 for false. Thus if you use a value
of 1 (true) then comments will not be allowed.
Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_nocomments(\%Event,1) ||
die "$0: Failed to set property - $LJ::Simple::error\n";
Setprop_picture_keyword($event,$keyword)Note that if the LiveJournal
object was created with either pics set to 0 or
with fast set to 1 then this function will not validate
the picture keyword before setting it.
Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_picture_keyword(\%Event,"Some photo") ||
die "$0: Failed to set property - $LJ::Simple::error\n";
Setprop_noemail($event,$onoff)1 for true and 0 for false.
Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_noemail(\%Event,1) ||
die "$0: Failed to set property - $LJ::Simple::error\n";
Setprop_unknown8bit($event,$onoff)1 for true and 0 for false.
Returns 1 on success, 0 on failure.
Example code:
$lj->Setprop_unknown8bit(\%Event,1) ||
die "$0: Failed to set property - $LJ::Simple::error\n";
PostEntry($event)$lj->NewEntry() and to have at least called
$lj->SetEntry().
On success a list containing the following is returned:
o The item_id as returned by the LiveJournal server
o The anum as returned by the LiveJournal server
o The item_id of the posted entry as used in HTML - that is the
value of C<($item_id * 256) + $anum)>
On failure undef is returned.
# Build the new entry
my %Event;
$lj->NewEntry(\%Event) ||
die "$0: Failed to create new journal entry - $LJ::Simple::error\n";
# Set the journal entry
$lj->SetEntry(\%Event,"foo") ||
die "$0: Failed set journal entry - $LJ::Simple::error\n";
# And post it
my ($item_id,$anum,$html_id)=$lj->PostEntry(\%Event);
defined $item_id ||
die "$0: Failed to submit new journal entry - $LJ::Simple::error\n";
EditEntry($event)item_id.
The entry should have been fetched from LiveJournal using the
$lj->GetEntries() function and then adjusted using the various
$lj->Set...() functions.
It should be noted that this function can be used to delete a journal entry
by setting the entry to a blank string, i.e. by using
$lj->SetEntry(\%Event,undef)
Returns 1 on success, 0 on failure.
Example:
# Fetch the most recent event
my %Events = ();
(defined $lj->GetEntries(\%Events,undef,"one",-1)) ||
die "$0: Failed to get entries - $LJ::Simple::error\n";
# Mark it as private
foreach (values %Entries) {
$lj->SetProtectPrivate($_);
$lj->EditEntry($_) ||
die "$0: Failed to edit entry - $LJ::Simple::error\n";
}
# Alternatively we could just delete it...
my $event=(values %Entries)[0];
$lj->SetEntry($event,undef);
$lj->EditEntry($event) ||
die "$0: Failed to edit entry - $LJ::Simple::error\n";
DeleteEntry($item_id)item_id.
On success 1 is returned; on failure 0 is returned.
Example:
$lj->DeleteEntry($some_item_id) ||
die "$0: Failed to delete journal entry - $LJ::Simple::error\n";
SyncItems($timestamp)time_t value or a previously
returned timestamp from this routine. If not used specify the undefined value
undef.
When specifying the time you must take into account the fact that the modification
or creation times of the entries in the LiveJournal database are stored as the
time local to the computer running the database rather than GMT. Due to this
it is safest to use the time from the latest item downloaded from the LiveJournal
from a previous SyncItems() call.
On success this routine will return a list which contains first the number of
valid items in the list and then a list of hashes which contain the details
of the items found. This routine can return an empty list which signifies that
no new items could be found. On failure undef is returned.
The format of the returned list is as follows. The list of hashes is ordered by the timestamps of the entries, oldest to newest.
@list = (
number of items returned,
{
item_id => Item_id of the entry changed
type => Type of entry
action => What happened to the entry
time_t => Time of change in Unix time (see note below)
timestamp => Timestamp from server
},
);
The type of entry can be one of the following letters:
L: Journal entries C: Comments T: To-do items
It should be noted that currently the LiveJournal system will only ever
return L types due to the C and T types not having been implemented
in the LiveJournal code yet.
The action of the entry can be either create for a new entry,
update for an entry which has been modified or del for a deleted entry.
The time_t value is probably going to be wrong; as far as the author of
this code can tell, you can not get the timezone of the server which is
serving out the request. This means that converting the timestamps
returned by the server from their format of YYYY-MM-DD hh:mm:ss into
a Unix time_t value is inaccurate at best since time_t is defined
as the number of seconds since 00:00 1st January 1970 GMT. Functions
like mktime() which can be used to create time_t values have to
assume that the data they are being given is valid for the timezone the
machine it is running on is actually in. Given the nature of the net
this is rarely the case. sigh I wish that the LJ developers had stored
timestamps in pure time_t in the database... and if they have done they
should provide a way for developers to get access to this as its much
more useful IMHO.
Given the above you're probably wondering why I included the time_t
value. Well, whilst the value isn't much use when it really comes down
to it, it is useful when it comes to sorting the list of entries as
all of the entries from the same server will be inaccurate to the same
amount.
The timestamp from server takes the format of YYYY-MM-DD hh:mm:ss
It should be noted that this routine can take a long time to return
if there are large numbers of entries to be returned. This is especially
true if you give undef as the timestamp.
Example code:
# All entries in the last day or so; this is fudged due to timezone # differences (WTF didn't they store stuff in GMT ?) my ($num_of_items,@lst)=$lj->SyncItems(time() - (86400 * 2));
(defined $num_of_items) ||
die "$0: Failed to sync - $LJ::Simple::error\n";
my $hr=undef;
print "Number of items: $num_of_items\n";
print "Item_id\tType\tAction\tTime_t\t\tTimestamp\n";
foreach $hr (@lst) {
print "$hr->{item_id}\t" .
"$hr->{type}\t" .
"$hr->{action}\t" .
"$hr->{time_t}\t" .
"$hr->{timestamp}\n";
}
There is also an example of how to work with all of the entries of a LiveJournal
shown in the examples/friends-only script which accompanies the LJ::Simple
distribution. This example script looks at a LiveJournal and makes sure that every
journal entry is at the very least marked as being friends-only.
GetEntries($hash_ref,$journal,$type,@opt)$type argument.
This routine will currently only allow you to get a maximum of 50 journal entries
thanks to restrictions imposed by LiveJournal servers. If you want to perform work
on every journal entry within a LiveJournal account then you should look at the
SyncItems() routine documented above.
The first argument - $hash_ref is a reference to a hash which will be filled
with the details of the journal entries downloaded. The key to this hash is the
item_id of the journal entries. The value is a hash reference which points to
a hash of the same type created by NewPost() and used by PostEntry() and
EditEntry(). The most sensible way to access this hash is to use the various
Get*() routines.
The second argument - $journal - is an optional argument set if the journal
to be accessed is a shared journal. If this is set then the name of shared journal
will be propogated into the entries returned in the hash reference $hash_ref as
if $lj-UseJournal($event,$journal)> was called. If not required set this to undef.
The third argument - $type - specifies how the journal entries are to be
pulled down. The contents of the fourth argument - @opt - will depend on the
value in the $type variable. Thus:
+-------+------------+------------------------------------------+ | $type | @opt | Comments | +-------+------------+------------------------------------------+ | day | $timestamp | Download a single day. $timestamp is a | | | | Unix timestamp for the required day | +-------+------------+------------------------------------------+ | lastn |$num,$before| Download a number of entries. $num has a | | | | maximum value of 50. If $num is undef | | | | then the default of 20 is used. $before | | | | is an optional value which specifies a | | | | date before which all entries must occur.| | | | The date is specified as a Unix | | | | timestamp. If not specified the value | | | | should be undef. | +-------+------------+------------------------------------------+ | one | $item_id | The unique ItemID for the entry to be | | | | downloaded. A value of -1 means to | | | | download the most recent entry | +-------+------------+------------------------------------------+ | sync | $date | Get journal entries since the given date.| | | | The date should be specified as a Unix | | | | timestamp. | +-------+------------+------------------------------------------+
If the operation is successful then $hash_ref is returned. On failure
undef is returned and $LJ::Simple::error is updated with the
reason for the error.
Example code:
The following code only uses a single $type from the above list; one.
However the hash of hashes returned is the same in every $type used. The
code below shows how to pull down the last journal entry posted and then uses
all of the various Get*() routines to decode the hash returned.
use POSIX;
my %Entries=();
(defined $lj->GetEntries(\%Entries,undef,"one",-1)) ||
die "$0: Failed to get entries - $LJ::Simple::error\n";
my $Entry=undef; my $Format="%-20s: %s\n";
foreach $Entry (values %Entries) {
# Get URL
my $url=$lj->GetURL($Entry);
(defined $url) && print "$url\n";
# Get ItemId
my ($item_id,$anum,$html_id)=$lj->GetItemId($Entry);
(defined $item_id) && printf($Format,"Item_id",$item_id);
# Get the subject
my $subj=$lj->GetSubject($Entry);
(defined $subj) && printf($Format,"Subject",$subj);
# Get the date entry was posted
my $timet=$lj->GetDate($Entry);
if (defined $timet) {
printf($Format,"Date",
strftime("%Y-%m-%d %H:%M:%S",localtime($timet)));
}
# Is entry protected ?
my $EntProt="";
my ($protect,@prot_opt)=$lj->GetProtect($Entry);
if (defined $protect) {
if ($protect eq "public") {
$EntProt="public";
} elsif ($protect eq "friends") {
$EntProt="friends only";
} elsif ($protect eq "groups") {
$EntProt=join("","only groups - ",join(", ",@prot_opt));
} elsif ($protect eq "private") {
$EntProt="private";
}
printf($Format,"Journal access",$EntProt);
}
## Properties
# Backdated ?
my $word="no";
my $prop=$lj->Getprop_backdate($Entry);
if ((defined $prop) && ($prop==1)) { $word="yes" }
printf($Format,"Backdated",$word);
# Preformatted ?
$word="no";
$prop=$lj->Getprop_preformatted($Entry);
if ((defined $prop) && ($prop==1)) { $word="yes" }
printf($Format,"Preformatted",$word);
# No comments allowed ?
$word="no";
$prop=$lj->Getprop_nocomments($Entry);
if ((defined $prop) && ($prop==1)) { $word="yes" }
printf($Format,"No comments",$word);
# Do not email comments ?
$word="no";
$prop=$lj->Getprop_noemail($Entry);
if ((defined $prop) && ($prop==1)) { $word="yes" }
printf($Format,"No emailed comments",$word);
# Unknown 8-bit ?
$word="no";
$prop=$lj->Getprop_unknown8bit($Entry);
if ((defined $prop) && ($prop==1)) { $word="yes" }
printf($Format,"Any 8 bit, non UTF-8",$word);
# Current music
$word="[None]";
$prop=$lj->Getprop_current_music($Entry);
if ((defined $prop) && ($prop ne "")) { $word=$prop }
printf($Format,"Current music",$word);
# Current mood [text]
$word="[None]";
$prop=$lj->Getprop_current_mood($Entry);
if ((defined $prop) && ($prop ne "")) { $word=$prop }
printf($Format,"Current mood",$word);
# Current mood [id]
$word="[None]";
$prop=$lj->Getprop_current_mood_id($Entry);
if ((defined $prop) && ($prop ne "")) { $word=$prop }
printf($Format,"Current mood_id",$word);
# Picture keyword
$word="[None]";
$prop=$lj->Getprop_picture_keyword($Entry);
if ((defined $prop) && ($prop ne "")) { $word=$prop }
printf($Format,"Picture keyword",$word);
# Finally output the actual journal entry
printf($Format,"Journal entry","");
my $text=$lj->GetEntry($Entry);
(defined $text) &&
print " ",join("\n ",split(/\n/,$text)),"\n\n";
}
GetDate($event)time_t (i.e. seconds
since epoch) value. Returns undef on failure.
Example code:
use POSIX; # For strftime()
## Get date
my $timet=$lj->GetDate(\%Event);
(defined $timet)
|| die "$0: Failed to set date of entry - $LJ::Simple::error\n";
# Get time list using localtime()
my @tm=localtime($timet);
($#tm<0) &&
die "$0: Failed to run localtime() on time_t $timet\n";
# Format date in the normal way used by LJ "YYYY-MM-DD hh:mm:ss"
my $jtime=strftime("%Y-%m-%d %H:%M:%S",@tm);
GetItemId($event)item_id, anum and HTMLised item_id which
can be used to contruct a URL suitable for accessing the item via the web.
Returns undef on failure. Note that you must only use this
routine on entries which have been returned by the GetEntries()
routine.
Example code:
my ($item_id,$anum,$html_id)=$lj->GetItemId(\%Event);
(defined $item_id)
|| die "$0: Failed to get item id - $LJ::Simple::error\n";
GetURL($event)undef on failure. Note that you must only use this
routine on entries which have been returned by the GetEntries()
routine.
Example code:
my $url=$lj->GetURL(\%Event);
(defined $url)
|| die "$0: Failed to get URL - $LJ::Simple::error\n";
system("netscape -remote 'openURL($url)'");
GetSubject($event)undef otherwise.
Example code:
my $subj=$lj->GetSubject(\%Event)
if (defined $subj) {
print "Subject: $subj\n";
}
GetEntry($event)undef on failure.
Example code:
my $ent = $lj->GetEntry(\%Event);
(defined $ent)
|| die "$0: Failed to get entry - $LJ::Simple::error\n";
print "Entry: $ent\n";
GetProtect($event)undef is returned.
There are several different types of protection which can be returned for a journal entry. These include public, friends only, specific friends groups and private. The list returned will always have the type of protection listed first followed by any details of that protection. Thus the list can contain:
("public")
A publically accessable journal entry
("friends")
Only friends may read the entry
("groups","group1" ...)
Only users listed in the friends groups given after the "groups"
may read the entry
("private")
Only the owner of the journal may read the entry
Example code:
my ($protect,@prot_opt)=$lj->GetProtect(\%Event);
(defined $protect) ||
die "$0: Failed to get entry protection type - $LJ::Simple::error\n";
if ($protect eq "public") {
print "Journal entry is public\n";
} elsif ($protect eq "friends") {
print "Journal entry only viewable by friends\n";
} elsif ($protect eq "groups") {
print "Journal entry only viewable by friends in the following groups:\n";
print join(", ",@prot_opt),"\n";
} elsif ($protect eq "private") {
print "Journal entry only viewable by the journal owner\n";
}
Getprop_backdate($event)1 if the entry is backdated, 0 if it is not. undef is returned in the
event of an error.
Example code:
my $prop=$lj->Getprop_backdate(\%Event);
(defined $prop) ||
die "$0: Failed to get property - $LJ::Simple::error\n";
if ($prop) {
print STDERR "Journal is backdated\n";
} else {
print STDERR "Journal is not backdated\n";
}
Getprop_current_mood($event)undef in the event
of an error.
Example code:
my $prop=$lj->Getprop_current_mood(\%Event);
(defined $prop) ||
die "$0: Failed to get property - $LJ::Simple::error\n";
if ($prop ne "") {
print STDERR "Journal has mood of $prop\n";
} else {
print STDERR "Journal has no mood set\n";
}
Getprop_current_mood_id($event)undef in
the event of an error.
Example code:
my $prop=$lj->Getprop_current_mood_id(\%Event);
(defined $prop) ||
die "$0: Failed to get property - $LJ::Simple::error\n";
if ($prop ne "") {
print STDERR "Journal has mood_id of $prop\n";
} else {
print STDERR "Journal has no mood_id set\n";
}
Getprop_current_music($event)undef in
the event of an error.
Example code:
my $prop=$lj->Getprop_current_music(\%Event);
(defined $prop) ||
die "$0: Failed to get property - $LJ::Simple::error\n";
if ($prop) {
print STDERR "Journal has the following music: $prop\n";
} else {
print STDERR "Journal has no music set for it\n";
}
Getprop_preformatted($event)1) if so, false (0) if not.
Example code:
$lj->Getprop_preformatted(\%Event) &&
print "Journal entry is preformatted\n";
Getprop_nocomments($event)1) if so, false (0) if not.
Example code:
$lj->Getprop_nocomments(\%Event) &&
print "Journal entry set to disallow comments\n";
Getprop_picture_keyword($event)undef in
the event of an error.
Example code:
my $prop=$lj->Getprop_picture_keyword(\%Event);
(defined $prop) ||
die "$0: Failed to get property - $LJ::Simple::error\n";
if ($prop) {
print STDERR "Journal has picture keyword $prop set\n";
} else {
print STDERR "Journal has no picture keyword set\n";
}
Getprop_noemail($event)1) if so comments should not be emailed and false
(0) if they should be emailed.
Example code:
$lj->Getprop_noemail(\%Event) &&
print "Comments to journal entry not emailed\n";
Getprop_unknown8bit($event)1) if so, false (0) if not.
Example code:
$lj->Getprop_unknown8bit(\%Event) &&
print "Journal entry contains 8-bit data not in UTF-8 format\n";
Simon Burr <simes@bpfh.net>
perl http://www.livejournal.com/
Copyright (c) 2002, Simon Burr <simes@bpfh.net> All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.