Merge branch 'main' of git.owlcode.tech:sergiotarxz/MTGPrint
This commit is contained in:
commit
247cb3cbfd
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user