Merge branch 'main' of git.owlcode.tech:sergiotarxz/MTGPrint

This commit is contained in:
Sergiotarxz 2024-05-23 13:00:07 +02:00
commit 247cb3cbfd
2 changed files with 148 additions and 99 deletions

View File

@ -28,20 +28,23 @@ sub _build__ua {
return Mojo::UserAgent->new; return Mojo::UserAgent->new;
} }
our $ERR_TOO_MUCH_CARDS = 'TOO_MUCH_CARDS'; our $ERR_TOO_MANY_CARDS = 'TOO_MANY_CARDS';
our $ERR_INVALID_CARD = 'INVALID_CARD'; our $ERR_INVALID_CARD = 'INVALID_CARD';
our $ERR_UNABLE_TO_FIND_IMAGE = 'UNABLE_TO_FIND_IMAGE'; our $ERR_UNABLE_TO_FIND_IMAGE = 'UNABLE_TO_FIND_IMAGE';
our $MAX_CARDS = 9 * 50; our $MAX_CARDS = 9 * 50;
sub _build__db_all_printings($self) { sub _build__db_all_printings ($self) {
return DBI->connect('dbi:SQLite:AllPrintings.sqlite'); return DBI->connect('dbi:SQLite:AllPrintings.sqlite');
} }
sub from_text( $self, $text ) { sub from_text ( $self, $text ) {
my $promise = Mojo::Promise->new;
{
my @lines = split /\n+/, $text; my @lines = split /\n+/, $text;
@lines = grep { $self->_filter_lines($_); } @lines; @lines = grep { $self->_filter_lines($_); } @lines;
if ( scalar @lines > $MAX_CARDS ) { if ( scalar @lines > $MAX_CARDS ) {
die $ERR_TOO_MUCH_CARDS; $promise->reject($ERR_TOO_MANY_CARDS);
next;
} }
my @cards = map { s/^\s*(.*?)\s*$/$1/; $self->_parse_card($_) } @lines; my @cards = map { s/^\s*(.*?)\s*$/$1/; $self->_parse_card($_) } @lines;
my $n_cards = 0; my $n_cards = 0;
@ -49,40 +52,52 @@ sub from_text( $self, $text ) {
$n_cards += $card->{quantity}; $n_cards += $card->{quantity};
} }
if ( $n_cards > $MAX_CARDS ) { if ( $n_cards > $MAX_CARDS ) {
die $ERR_TOO_MUCH_CARDS; $promise->reject($ERR_TOO_MANY_CARDS);
next;
} }
@cards = map { $self->_fill_scryfall_id($_) } @cards; eval {
my $promise = Mojo::Promise->new; @cards = map { $self->_fill_scryfall_id($_) } @cards;
$self->_get_cards_images(\@cards)->then(sub ($images) { };
my $real_number_of_cards = 0; if ($@) {
for my $card (@cards) { $promise->reject($@);
$real_number_of_cards += $card->{quantity} * scalar keys $images->{$card->{scryfallId}}->%*; next;
}
$self->_get_cards_images( \@cards )->then(
sub ($images) {
my $real_number_of_cards = 0;
for my $card (@cards) {
$real_number_of_cards += $card->{quantity} * scalar
keys $images->{ $card->{scryfallId} }->%*;
}
if ( $real_number_of_cards > $MAX_CARDS ) {
$promise->reject($ERR_TOO_MANY_CARDS);
}
my $pdf = $self->_generate_pdf( \@cards, $images );
$promise->resolve($pdf);
} }
if ($real_number_of_cards > $MAX_CARDS) { )->catch(
$promise->reject($ERR_TOO_MUCH_CARDS); sub ($err) {
$promise->reject($err);
} }
my $pdf = $self->_generate_pdf(\@cards, $images); );
$promise->resolve($pdf); }
})->catch(sub ($err) {
$promise->reject($err);
});
return $promise; return $promise;
} }
sub _generate_pdf($self, $cards, $images) { sub _generate_pdf ( $self, $cards, $images ) {
my $number_image = 0; my $number_image = 0;
my $pdf = PDF::API2->new; my $pdf = PDF::API2->new;
my $page = $pdf->page; my $page = $pdf->page;
$page->size('A4'); $page->size('A4');
my @rectangle = $page->size; my @rectangle = $page->size;
my $n_pixels_per_cm = $rectangle[2] / 21.0; my $n_pixels_per_cm = $rectangle[2] / 21.0;
for my $card (@$cards) { for my $card (@$cards) {
my $images_card = $images->{$card->{scryfallId}}; my $images_card = $images->{ $card->{scryfallId} };
for my $image_kind (keys %$images_card) { for my $image_kind ( keys %$images_card ) {
for (my $i = 0; $i < $card->{quantity}; $i++) { for ( my $i = 0 ; $i < $card->{quantity} ; $i++ ) {
if ($number_image > 8) { if ( $number_image > 8 ) {
$number_image = 0; $number_image = 0;
$page = $pdf->page; $page = $pdf->page;
$page->size('A4'); $page->size('A4');
} }
my $margin_left = 25; my $margin_left = 25;
@ -91,12 +106,17 @@ sub _generate_pdf($self, $cards, $images) {
my $mtg_card_height = 8.89; my $mtg_card_height = 8.89;
my $small_line_to_cut_better_size = 0.5; my $small_line_to_cut_better_size = 0.5;
open my $fh, '<', \$images_card->{$image_kind}; open my $fh, '<', \$images_card->{$image_kind};
my $image = $pdf->image($fh, format => 'jpeg'); my $image = $pdf->image( $fh, format => 'jpeg' );
my $page_position_x = $number_image % 3; my $page_position_x = $number_image % 3;
my $page_position_y = abs(int($number_image / 3) - 2); my $page_position_y = abs( int( $number_image / 3 ) - 2 );
$page->object($image, $page->object(
$margin_bottom + $page_position_x * $mtg_card_width * $n_pixels_per_cm + $small_line_to_cut_better_size * $page_position_x, $image,
$margin_bottom + $page_position_y * $mtg_card_height * $n_pixels_per_cm + $small_line_to_cut_better_size * $page_position_x, $margin_bottom +
$page_position_x * $mtg_card_width * $n_pixels_per_cm +
$small_line_to_cut_better_size * $page_position_x,
$margin_bottom +
$page_position_y * $mtg_card_height * $n_pixels_per_cm +
$small_line_to_cut_better_size * $page_position_x,
$n_pixels_per_cm * $mtg_card_width, $n_pixels_per_cm * $mtg_card_width,
); );
$number_image++; $number_image++;
@ -106,7 +126,7 @@ sub _generate_pdf($self, $cards, $images) {
return $pdf->to_string; return $pdf->to_string;
} }
sub _get_cards_images( $self, $cards ) { sub _get_cards_images ( $self, $cards ) {
my %card_images; my %card_images;
my @promises; my @promises;
my $ua = $self->_ua; my $ua = $self->_ua;
@ -114,11 +134,11 @@ sub _get_cards_images( $self, $cards ) {
my $scryfallId = $card->{scryfallId}; my $scryfallId = $card->{scryfallId};
my ( $first, $second ) = $scryfallId =~ /^(.)(.)/; my ( $first, $second ) = $scryfallId =~ /^(.)(.)/;
my $url_front = my $url_front =
"https://cards.scryfall.io/normal/front/$first/$second/$scryfallId.jpg"; "https://cards.scryfall.io/normal/front/$first/$second/$scryfallId.jpg";
my $url_back = my $url_back =
"https://cards.scryfall.io/normal/back/$first/$second/$scryfallId.jpg"; "https://cards.scryfall.io/normal/back/$first/$second/$scryfallId.jpg";
my $promise_front = Mojo::Promise->new; my $promise_front = Mojo::Promise->new;
my $promise_back = Mojo::Promise->new; my $promise_back = Mojo::Promise->new;
$ua->get_p($url_front)->then( $ua->get_p($url_front)->then(
sub ($res) { sub ($res) {
my $content_type = $res->result->headers->content_type; my $content_type = $res->result->headers->content_type;
@ -157,22 +177,26 @@ sub _get_cards_images( $self, $cards ) {
push @promises, $promise_back; push @promises, $promise_back;
} }
my $final_promise = Mojo::Promise->new; my $final_promise = Mojo::Promise->new;
Mojo::Promise->all(@promises)->then(sub { Mojo::Promise->all(@promises)->then(
$final_promise->resolve(\%card_images); sub {
})->catch(sub ($err) { $final_promise->resolve( \%card_images );
$final_promise->reject($err); }
}); )->catch(
sub ($err) {
$final_promise->reject($err);
}
);
return $final_promise; return $final_promise;
} }
sub _fill_scryfall_id( $self, $card ) { sub _fill_scryfall_id ( $self, $card ) {
if ( $card->{type} eq 'token' ) { if ( $card->{type} eq 'token' ) {
return $self->_fill_scryfall_id_token($card); return $self->_fill_scryfall_id_token($card);
} }
return $self->_fill_scryfall_id_card($card); return $self->_fill_scryfall_id_card($card);
} }
sub _fill_scryfall_id_card( $self, $card ) { sub _fill_scryfall_id_card ( $self, $card ) {
my $db = $self->_db_all_printings; my $db = $self->_db_all_printings;
my $query = <<'EOF'; my $query = <<'EOF';
select scryfallId select scryfallId
@ -194,7 +218,7 @@ EOF
return { %$card, }; return { %$card, };
} }
sub _fill_scryfall_id_token( $self, $token ) { sub _fill_scryfall_id_token ( $self, $token ) {
my $db = $self->_db_all_printings; my $db = $self->_db_all_printings;
my $query = <<'EOF'; my $query = <<'EOF';
SELECT scryfallId SELECT scryfallId
@ -203,28 +227,30 @@ FROM tokens
ON tokens.uuid = tokenIdentifiers.uuid ON tokens.uuid = tokenIdentifiers.uuid
WHERE tokens.name = ? WHERE tokens.name = ?
EOF EOF
my @args = ($token->{name}); my @args = ( $token->{name} );
$query .= ' AND LOWER(tokens.setCode) = LOWER(?)' if defined $token->{set_code}; $query .= ' AND LOWER(tokens.setCode) = LOWER(?)'
if defined $token->{set_code};
push @args, $token->{set_code} if defined $token->{set_code}; push @args, $token->{set_code} if defined $token->{set_code};
$query .= ' LIMIT 1'; $query .= ' LIMIT 1';
my $result = $db->selectrow_hashref( $query, undef, @args); my $result = $db->selectrow_hashref( $query, undef, @args );
if ( !defined $result ) { if ( !defined $result ) {
$self->last_invalid_card( my $last_invalid_card = "@{[$token->{quantity}]} @{[$token->{name}]}";
"@{[$token->{quantity}]} @{[$token->{name}]} @{[$token->{set_code}]}" $last_invalid_card .= " @{[$token->{set_code}]}"
); if defined $token->{set_code};
$self->last_invalid_card($last_invalid_card);
die $ERR_INVALID_CARD; die $ERR_INVALID_CARD;
} }
$token->{scryfallId} = $result->{scryfallId}; $token->{scryfallId} = $result->{scryfallId};
return {%$token}; return {%$token};
} }
sub _filter_lines( $self, $arg ) { sub _filter_lines ( $self, $arg ) {
return 0 if $arg =~ /^\w+:\s*$/; return 0 if $arg =~ /^\w+:\s*$/;
return 0 if $arg =~ /^\s*$/; return 0 if $arg =~ /^\s*$/;
return 1; return 1;
} }
sub _parse_token( $self, $line ) { sub _parse_token ( $self, $line ) {
my ( $quantity, $name, $set_code ); my ( $quantity, $name, $set_code );
if ( if (
!( !(
@ -245,7 +271,7 @@ sub _parse_token( $self, $line ) {
}; };
} }
sub _parse_card( $self, $line ) { sub _parse_card ( $self, $line ) {
if ( $line !~ /\(/ ) { if ( $line !~ /\(/ ) {
return $self->_parse_token($line); return $self->_parse_token($line);
} }

View File

@ -20,12 +20,12 @@ has _ua => ( is => 'lazy', );
has _last_offset_update => ( is => 'rw', ); has _last_offset_update => ( is => 'rw', );
sub _build__token($self) { sub _build__token ($self) {
require TgMagicPdf; require TgMagicPdf;
return TgMagicPdf->new->config->{telegram}{token}; return TgMagicPdf->new->config->{telegram}{token};
} }
sub run($self) { sub run ($self) {
$self->_dispatch_updates; $self->_dispatch_updates;
Mojo::IOLoop->recurring( Mojo::IOLoop->recurring(
5 => sub { 5 => sub {
@ -34,7 +34,7 @@ sub run($self) {
); );
} }
sub _dispatch_updates($self) { sub _dispatch_updates ($self) {
my $updates_p = $self->_get_updates; my $updates_p = $self->_get_updates;
$updates_p->then( $updates_p->then(
sub ($res) { sub ($res) {
@ -45,12 +45,12 @@ sub _dispatch_updates($self) {
} }
)->catch( )->catch(
sub ($err) { sub ($err) {
say $err; warn $err;
} }
); );
} }
sub _dispatch_update( $self, $update ) { sub _dispatch_update ( $self, $update ) {
if ( !defined $self->_last_offset_update if ( !defined $self->_last_offset_update
|| $self->_last_offset_update < $update->{update_id} ) || $self->_last_offset_update < $update->{update_id} )
{ {
@ -61,68 +61,91 @@ sub _dispatch_update( $self, $update ) {
} }
} }
sub _handle_message( $self, $message ) { sub sendMessage ( $self, $chat_id, $text ) {
my $url = "@{[$self->_tg_root]}/sendMessage";
my $ua = $self->_ua;
$ua->post_p( $url, json => { chat_id => $chat_id, text => $text } );
}
sub _handle_message ( $self, $message ) {
my $chat_type = $message->{chat}{type}; my $chat_type = $message->{chat}{type};
if ( $chat_type ne 'private' ) { if ( $chat_type ne 'private' ) {
return; return;
} }
my $chat_id = $message->{chat}{id}; my $chat_id = $message->{chat}{id};
my $text = $message->{text}; my $text = $message->{text};
if (!defined $text) { if ( !defined $text ) {
return; return;
} }
$self->sendMessage( $chat_id,
'Got your message, attempting to generate a pdf.' );
my $pdf_builder = TgMagicPdf::PdfBuilder->new; my $pdf_builder = TgMagicPdf::PdfBuilder->new;
my $pdf_p; $pdf_builder->from_text($text)->then(
eval { sub ($pdf) {
$pdf_p = $pdf_builder->from_text($text); $self->sendDocument( $chat_id, 'mtgprint.pdf', $pdf );
$pdf_p->then(sub ($pdf) {
$self->sendDocument($chat_id, 'mtgprint.pdf', $pdf);
})->catch(sub ($err) {
if ($err eq $TgMagicPdf::PdfBuilder::ERR_INVALID_CARD) {
warn $pdf_builder->last_invalid_card;
}
if ($err eq $TgMagicPdf::PdfBuilder::ERR_UNABLE_TO_FIND_IMAGE) {
warn $pdf_builder->last_invalid_card;
}
warn $err
});
};
if ($@) {
if ($@ eq $TgMagicPdf::PdfBuilder::ERR_INVALID_CARD) {
warn $pdf_builder->last_invalid_card;
} }
if ($@ eq $TgMagicPdf::PdfBuilder::ERR_UNABLE_TO_FIND_IMAGE) { )->catch(
warn $pdf_builder->last_invalid_card; sub ($err) {
my $match = 0;
for my $known_error (
$TgMagicPdf::PdfBuilder::ERR_INVALID_CARD,
$TgMagicPdf::PdfBuilder::ERR_UNABLE_TO_FIND_IMAGE,
$TgMagicPdf::PdfBuilder::ERR_TOO_MANY_CARDS
)
{
$match = 1 if ( index( $err, $known_error ) != -1 );
}
if ($match) {
my $error_to_user .= $err =~ s/ at.*$//r;
if ( -1 != index $err,
$TgMagicPdf::PdfBuilder::ERR_INVALID_CARD )
{
$error_to_user .= ' ' . $pdf_builder->last_invalid_card;
}
if ( -1 != index $err,
$TgMagicPdf::PdfBuilder::ERR_UNABLE_TO_FIND_IMAGE )
{
$error_to_user .= ' ' . $pdf_builder->last_invalid_card;
}
$self->sendMessage( $chat_id,
'I could not process your deck take a look to this details: '
. $error_to_user );
return;
}
warn $err;
$self->sendMessage( $chat_id,
'I could not process your deck because of a server error' );
} }
warn $@; );
return;
}
} }
sub sendDocument($self, $chat_id, $filename, $file_contents) { sub sendDocument ( $self, $chat_id, $filename, $file_contents ) {
my $url = "@{[$self->_tg_root]}/sendDocument"; my $url = "@{[$self->_tg_root]}/sendDocument";
my $ua = $self->_ua; my $ua = $self->_ua;
my $res = $ua->post($url, form => { my $res = $ua->post_p(
chat_id => $chat_id, $url,
document => { form => {
content => $file_contents, chat_id => $chat_id,
'Content-Type' => 'application/pdf', document => {
filename => $filename, content => $file_contents,
}, 'Content-Type' => 'application/pdf',
}); filename => $filename,
},
}
);
} }
sub _pdf_builder { sub _pdf_builder {
return TgMagicPdf::PdfBuilder->new; return TgMagicPdf::PdfBuilder->new;
} }
sub _build__ua($self) { sub _build__ua ($self) {
my $ua = Mojo::UserAgent->new; my $ua = Mojo::UserAgent->new;
$ua->inactivity_timeout(30); $ua->inactivity_timeout(30);
return $ua; return $ua;
} }
sub _get_updates($self) { sub _get_updates ($self) {
my $ua = $self->_ua; my $ua = $self->_ua;
my $url = "@{[$self->_tg_root]}/getUpdates"; my $url = "@{[$self->_tg_root]}/getUpdates";
my %params; my %params;
@ -132,7 +155,7 @@ sub _get_updates($self) {
return $ua->post_p( $url, json => {%params} ); return $ua->post_p( $url, json => {%params} );
} }
sub _build__tg_root($self) { sub _build__tg_root ($self) {
return "https://api.telegram.org/bot@{[$self->_token]}"; return "https://api.telegram.org/bot@{[$self->_token]}";
} }
1; 1;