use Mojo::Base -base;
use Mojo::Util 'trim';
use constant DEBUG => $ENV{LINK_EMBEDDER_DEBUG} || 0;
my %DOM_SEL = (
':desc' => ['meta[property="og:description"]', 'meta[name="twitter:description"]', 'meta[name="description"]'],
':image' => ['meta[property="og:image"]', 'meta[property="og:image:url"]', 'meta[name="twitter:image"]'],
':site_name' => ['meta[property="og:site_name"]', 'meta[property="twitter:site"]'],
':title' => ['meta[property="og:title"]', 'meta[name="twitter:title"]', 'title'],
);
my @JSON_ATTRS = (
'author_name', 'author_url', 'cache_age', 'height', 'provider_name', 'provider_url',
'thumbnail_height', 'thumbnail_url', 'thumbnail_width', 'title', 'type', 'url',
'version', 'width'
);
has author_name => undef;
has author_url => undef;
has cache_age => 0;
has description => '';
has error => undef; # {message => "", code => ""}
has height => sub { $_[0]->type =~ /^photo|video$/ ? 0 : undef };
has placeholder_url => sub {
return sprintf 'http://placehold.it/200x200?text=%s', shift->provider_name;
};
has provider_name => sub {
return undef unless my $name = shift->url->host;
return $name =~ /([^\.]+)\.(\w+)$/ ? ucfirst $1 : $name;
};
has provider_url => sub { $_[0]->url->host ? $_[0]->url->clone->path('/') : undef };
has template => sub { [__PACKAGE__, sprintf '%s.html.ep', $_[0]->type] };
has thumbnail_height => undef;
has thumbnail_url => undef;
has thumbnail_width => undef;
has title => undef;
has type => 'link';
has ua => undef; # Mojo::UserAgent object
has url => undef; # Mojo::URL
has version => '1.0';
has width => sub { $_[0]->type =~ /^photo|video$/ ? 0 : undef };
sub html {
my $self = shift;
my $template = Mojo::Loader::data_section(@{$self->template}) or return '';
my $output = Mojo::Template->new({auto_escape => 1, prepend => 'my $l=shift'})->render($template, $self);
die $output if ref $output;
return $output;
}
sub learn_p {
my $self = shift;
my $url = $self->url;
return $self->ua->get_p($url)->then(sub { $self->_learn(shift) });
}
sub TO_JSON {
my $self = shift;
my %json;
for my $attr (grep { defined $self->$_ } @JSON_ATTRS) {
$json{$attr} = $self->$attr;
$json{$attr} = "$json{$attr}" if $attr =~ /url$/;
}
$json{html} = $self->html unless $self->type eq 'link';
return \%json;
}
sub _dump { Mojo::Util::dumper($_[0]->TO_JSON); }
sub _el {
my ($self, $dom, @sel) = @_;
@sel = @{$DOM_SEL{$sel[0]}} if $DOM_SEL{$sel[0]};
for my $sel (@sel) {
my $e = $dom->at($sel) or next;
my ($val) = grep {$_} map { trim($_ // '') } $e->{content}, $e->{value}, $e->{href}, $e->text, $e->all_text;
return $val if defined $val;
}
return '';
}
sub _learn {
my ($self, $tx) = @_;
my $ct = $tx->res->headers->content_type || '';
$self->type('photo')->_learn_from_url if $ct =~ m!^image/!;
$self->type('video')->_learn_from_url if $ct =~ m!^video/!;
$self->type('rich')->_learn_from_url if $ct =~ m!^text/plain!;
$self->type('rich')->_learn_from_dom($tx->res->dom) if $ct =~ m!^text/html!;
return $self;
}
sub _learn_from_dom {
my ($self, $dom) = @_;
my $v;
$self->author_name($v) if $v = $self->_el($dom, '[itemprop="author"] [itemprop="name"]');
$self->author_url($v) if $v = $self->_el($dom, '[itemprop="author"] [itemprop="email"]');
$self->description($v) if $v = $self->_el($dom, ':desc');
$self->thumbnail_height($v) if $v = $self->_el($dom, 'meta[property="og:image:height"]');
$self->thumbnail_url($v) if $v = $self->_el($dom, ':image');
$self->thumbnail_width($v) if $v = $self->_el($dom, 'meta[property="og:image:width"]');
$self->title($v) if $v = $self->_el($dom, ':title');
return $self;
}
sub _learn_from_json {
my ($self, $tx) = @_;
my $json = $tx->res->json;
warn "[LinkEmbedder] " . $tx->res->text . "\n" if DEBUG;
$self->{$_} ||= $json->{$_} for keys %$json;
$self->{error} = {message => $self->{error}} if defined $self->{error} and !ref $self->{error};
$self->{error}{code} = $self->{status} if $self->{status} and $self->{status} =~ /^\d+$/;
return $self;
}
sub _learn_from_url {
my $self = shift;
my $path = $self->url->path;
return $self->title(@$path ? $path->[-1] : 'Image');
}
1;
=encoding utf8
=head1 NAME
LinkEmbedder::Link - Meta information for an URL
=head1 SYNOPSIS
See L<LinkEmbedder>.
=head1 DESCRIPTION
L<LinkEmbedder::Link> is a class representing an expanded URL.
=head1 ATTRIBUTES
=head2 author_name
$str = $self->author_name;
Might hold the name of the author of L</url>.
=head2 author_url
$str = $self->author_name;
Might hold an URL to the author.
=head2 cache_age
$int = $self->cache_age;
The suggested cache lifetime for this resource, in seconds.
=head2 description
$str = $self->description;
Description of the L</url>. Might be C<undef()>.
=head2 error
$hash_ref = $self->author_name;
C<undef()> on success, hash-ref on error. Example:
{message => "Oops!", code => 500};
=head2 height
$int = $self->height;
The height of L</html> in pixels. Might be C<undef>.
=head2 provider_name
$str = $self->provider_name;
Name of the provider of L</url>.
=head2 provider_url
$str = $self->provider_name;
Main URL to the provider's home page.
=head2 template
$array_ref = $self->provider_name;
Used to figure out which template to use to render L</html>. Example:
["LinkEmbedder::Link", "rich.html.ep];
=head2 thumbnail_height
$int = $self->thumbnail_height;
The height of the L</thumbnail_url> in pixels. Might be C<undef>.
=head2 thumbnail_url
$str = $self->thumbnail_url;
URL to the thumbnail which can be used in L</html>.
=head2 thumbnail_width
$int = $self->thumbnail_width;
The width of the L</thumbnail_url> in pixels. Might be C<undef>.
=head2 title
$str = $self->title;
Title/heading of the L</url>. Might be C<undef()>.
=head2 type
$str = $self->title;
oEmbed type of URL: link, photo, rich or video.
=head2 ua
$ua = $self->ua;
Holds a L<Mojo::UserAgent> object.
=head2 url
$str = $self->url;
The resource to fetch.
=head2 version
$str = $self->version;
oEmbed version. Example: "1.0".
=head2 width
$int = $self->width;
The width in pixels. Might be C<undef>.
=head1 METHODS
=head2 html
$str = $self->html;
Returns the L</url> as rich markup, if possible.
=head2 learn_p
$promise = $self->learn_p->then(sub { my $self = shift; });
Used to learn about the L</url>.
=head1 AUTHOR
Jan Henning Thorsen
=head1 SEE ALSO
L<LinkEmbedder>
=cut
__DATA__
@@ iframe.html.ep
<iframe class="le-<%= $l->type %> le-provider-<%= lc $l->provider_name %>" width="<%= $l->width || 600 %>" height="<%= $l->height || 400 %>" style="border:0;width:100%" frameborder="0" allowfullscreen src="<%= $l->{iframe_src} %>"></iframe>
@@ link.html.ep
<a class="le-<%= $l->type %>" href="<%= $l->url %>" title="<%= $l->title || '' %>"><%= Mojo::Util::url_unescape($l->url) %></a>
@@ paste.html.ep
<div class="le-paste le-provider-<%= lc $l->provider_name %> le-<%= $l->type %>">
<div class="le-meta">
<span class="le-provider-link"><a href="<%= $l->provider_url %>"><%= $l->provider_name %></a></span>
<span class="le-goto-link"><a href="<%= $l->url %>" title="<%= $l->title %>"><%= $l->{paste_name} || $l->author_name || 'View' %></a></span>
</div>
<pre><%= $l->{paste} || '' %></pre>
</div>
@@ photo.html.ep
<div class="le-<%= $l->type %> le-provider-<%= lc $l->provider_name %>">
<img src="<%= $l->url %>" alt="<%= $l->title %>">
</div>
@@ rich.html.ep
% if ($l->title) {
% if (my $thumbnail_url = $l->thumbnail_url || $l->placeholder_url) {
<div class="le-card le-image-card le-<%= $l->type %> le-provider-<%= lc $l->provider_name %>">
<a href="<%= $l->url %>" class="le-thumbnail<%= $l->thumbnail_url ? '' : '-placeholder' %>">
<img src="<%= $thumbnail_url %>" alt="<%= $l->author_name || 'Placeholder' %>">
</a>
% } else {
<div class="le-card le-<%= $l->type %> le-provider-<%= lc $l->provider_name %>">
% }
<h3><%= $l->title %></h3>
% if ($l->description) {
<p class="le-description"><%= $l->description %></p>
% }
<div class="le-meta">
% if ($l->author_name) {
<span class="le-author-link"><a href="<%= $l->author_url || $l->url %>"><%= $l->author_name %></a></span>
% }
<span class="le-goto-link"><a href="<%= $l->url %>"><span><%= $l->url %></span></a></span>
</div>
</div>
% } else {
<a class="le-<%= $l->type %> le-provider-<%= lc $l->provider_name %>" href="<%= $l->url %>"><%= Mojo::Util::url_unescape($l->url) %></a>
% }
@@ video.html.ep
<video class="le-<%= $l->type %> le-provider-<%= lc $l->provider_name %>" height="640" width="480" preload="metadata" controls>
% for my $s (@{$l->{sources} || []}) {
<source src="<%= $s->{url} %>" type="<%= $s->{type} || '' %>">
% }
<p>Your browser does not support the video tag.</p>
</video>