travelynx/lib/Travelynx/Helper/Traewelling.pm
Daniel Friesel 89e709d8d5 Allow linking a Träwelling account, auto-sync Träwelling→travelynx
travelynx→Träwelling is still work-in-progress

Squashed commit of the following:

commit 97faa6e2e6c8d20fba30f2d0f6e78187ceeb72e6
Author: Daniel Friesel <derf@finalrewind.org>
Date:   Wed Sep 30 18:50:05 2020 +0200

    improve traewelling log and tx handling

commit 487d7dd728b9d45b731bdc7098cf3358ea2e206e
Author: Daniel Friesel <derf@finalrewind.org>
Date:   Wed Sep 30 18:02:41 2020 +0200

    add missing traewelling template

commit 0148da2f48d9a52dcddc0ab81f83d8f8ac3062ab
Author: Daniel Friesel <derf@finalrewind.org>
Date:   Wed Sep 30 18:02:35 2020 +0200

    improve traewelling pull sync

commit 4861a9750f9f2d7621043361d0af6b0a8869a0df
Author: Daniel Friesel <derf@finalrewind.org>
Date:   Tue Sep 29 22:14:24 2020 +0200

    wip checkin from traewelling

commit f6aeb6f06998a2a7a80f63a7b1b688b1a26b66bd
Author: Daniel Friesel <derf@finalrewind.org>
Date:   Tue Sep 29 18:37:53 2020 +0200

    refactor traewelling integration. login and logout are less of a hack now.

    checkin and checkout are not supported at the moment.
2020-09-30 19:12:29 +02:00

332 lines
7.5 KiB
Perl

package Travelynx::Helper::Traewelling;
use strict;
use warnings;
use 5.020;
use Mojo::Promise;
sub new {
my ( $class, %opt ) = @_;
my $version = $opt{version};
$opt{header}
= { 'User-Agent' =>
"travelynx/${version} on $opt{root_url} +https://finalrewind.org/projects/travelynx"
};
return bless( \%opt, $class );
}
sub get_status_p {
my ( $self, %opt ) = @_;
my $username = $opt{username};
my $token = $opt{token};
my $promise = Mojo::Promise->new;
my $header = {
'User-Agent' => $self->{header}{'User-Agent'},
'Authorization' => "Bearer $token",
};
$self->{user_agent}->request_timeout(20)
->get_p( "https://traewelling.de/api/v0/user/${username}" => $header )
->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg = "HTTP $err->{code} $err->{message}";
$promise->reject($err_msg);
return;
}
else {
if ( my $status = $tx->result->json->{statuses}{data}[0] ) {
my $strp = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S.000000Z',
time_zone => 'UTC',
);
my $status_id = $status->{id};
my $message = $status->{body};
my $checkin_at
= $strp->parse_datetime( $status->{created_at} );
my $dep_dt = $strp->parse_datetime(
$status->{train_checkin}{departure} );
my $arr_dt = $strp->parse_datetime(
$status->{train_checkin}{arrival} );
my $dep_eva
= $status->{train_checkin}{origin}{ibnr};
my $arr_eva
= $status->{train_checkin}{destination}{ibnr};
my $dep_name
= $status->{train_checkin}{origin}{name};
my $arr_name
= $status->{train_checkin}{destination}{name};
my $category
= $status->{train_checkin}{hafas_trip}{category};
my $trip_id
= $status->{train_checkin}{hafas_trip}{trip_id};
my $linename
= $status->{train_checkin}{hafas_trip}{linename};
my ( $train_type, $train_line ) = split( qr{ }, $linename );
$promise->resolve(
{
status_id => $status_id,
message => $message,
checkin => $checkin_at,
dep_dt => $dep_dt,
dep_eva => $dep_eva,
dep_name => $dep_name,
arr_dt => $arr_dt,
arr_eva => $arr_eva,
arr_name => $arr_name,
trip_id => $trip_id,
train_type => $train_type,
line => $linename,
line_no => $train_line,
category => $category,
}
);
return;
}
else {
$promise->reject("unknown error");
return;
}
}
}
)->catch(
sub {
my ($err) = @_;
$promise->reject($err);
return;
}
)->wait;
return $promise;
}
sub get_user_p {
my ( $self, $uid, $token ) = @_;
my $ua = $self->{user_agent}->request_timeout(20);
my $header = {
'User-Agent' => $self->{header}{'User-Agent'},
'Authorization' => "Bearer $token",
};
my $promise = Mojo::Promise->new;
$ua->get_p( "https://traewelling.de/api/v0/getuser" => $header )->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg
= "HTTP $err->{code} $err->{message} bei Abfrage der Nutzerdaten";
$promise->reject($err_msg);
return;
}
else {
my $user_data = $tx->result->json;
$self->{model}->set_user(
uid => $uid,
trwl_id => $user_data->{id},
screen_name => $user_data->{name},
user_name => $user_data->{username},
);
$promise->resolve;
return;
}
}
)->catch(
sub {
my ($err) = @_;
$promise->reject("$err bei Abfrage der Nutzerdaten");
return;
}
)->wait;
return $promise;
}
sub login_p {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
my $email = $opt{email};
my $password = $opt{password};
my $ua = $self->{user_agent}->request_timeout(20);
my $request = {
email => $email,
password => $password,
};
my $promise = Mojo::Promise->new;
my $token;
$ua->post_p(
"https://traewelling.de/api/v0/auth/login" => $self->{header} =>
json => $request )->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg = "HTTP $err->{code} $err->{message} bei Login";
$promise->reject($err_msg);
return;
}
else {
$token = $tx->result->json->{token};
$self->{model}->link(
uid => $uid,
email => $email,
token => $token
);
return $self->get_user_p( $uid, $token );
}
}
)->then(
sub {
$promise->resolve;
return;
}
)->catch(
sub {
my ($err) = @_;
if ($token) {
# We have a token, but couldn't complete the login. For now, we
# solve this by logging out and invalidating the token.
$self->logout_p(
uid => $uid,
token => $token
)->finally(
sub {
$promise->reject($err);
return;
}
);
}
else {
$promise->reject($err);
}
return;
}
)->wait;
return $promise;
}
sub logout_p {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
my $token = $opt{token};
my $ua = $self->{user_agent}->request_timeout(20);
my $header = {
'User-Agent' => $self->{header}{'User-Agent'},
'Authorization' => "Bearer $token",
};
my $request = {};
$self->{model}->unlink( uid => $uid );
my $promise = Mojo::Promise->new;
$ua->post_p(
"https://traewelling.de/api/v0/auth/logout" => $header => json =>
$request )->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg = "HTTP $err->{code} $err->{message}";
$promise->reject($err_msg);
return;
}
else {
$promise->resolve;
return;
}
}
)->catch(
sub {
my ($err) = @_;
$promise->reject($err);
return;
}
)->wait;
return $promise;
}
sub checkin {
my ( $self, $uid ) = @_;
if ( my $token = $self->get_traewelling_push_token($uid) ) {
my $user = $self->get_user_status;
# TODO delete previous traewelling status if the train's destination has been changed
# TODO delete traewelling status when undoing a travelynx checkin
if ( $user->{checked_in} and $user->{extra_data}{trip_id} ) {
my $traewelling = $self->{model}->get($uid);
if ( $traewelling->{data}{trip_id} eq $user->{extra_data}{trip_id} )
{
return;
}
my $header = {
'User-Agent' => 'travelynx/' . $self->{version},
'Authorization' => "Bearer $token",
};
my $request = {
tripID => $user->{extra_data}{trip_id},
start => q{} . $user->{dep_eva},
destination => q{} . $user->{arr_eva},
};
my $trip_req = sprintf(
"tripID=%s&lineName=%s%%20%s&start=%s",
$user->{extra_data}{trip_id}, $user->{train_type},
$user->{train_line} // $user->{train_no}, $user->{dep_eva}
);
$self->{user_agent}->request_timeout(20)
->get_p(
"https://traewelling.de/api/v0/trains/trip?$trip_req" =>
$header )->then(
sub {
return $self->{user_agent}->request_timeout(20)
->post_p(
"https://traewelling.de/api/v0/trains/checkin" =>
$header => json => $request );
}
)->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg = "HTTP $err->{code} $err->{message}";
$self->mark_trwl_checkin_error( $uid, $user, $err_msg );
}
else {
# TODO check for traewelling error ("error" key in response)
# TODO store ID of resulting status (request /user/{name} and store status ID)
$self->mark_trwl_checkin_success( $uid, $user );
# mark success: checked into (trip_id, start, destination)
}
}
)->catch(
sub {
my ($err) = @_;
$self->mark_trwl_checkin_error( $uid, $user, $err );
}
)->wait;
}
}
}
1;