travelynx/lib/Travelynx/Helper/Traewelling.pm

390 lines
9.2 KiB
Perl
Raw Normal View History

package Travelynx::Helper::Traewelling;
2023-07-03 15:59:25 +00:00
# Copyright (C) 2020-2023 Birte Kristina Friesel
# Copyright (C) 2023 networkException <git@nwex.de>
2020-11-27 21:12:56 +00:00
#
2021-01-29 17:32:13 +00:00
# SPDX-License-Identifier: AGPL-3.0-or-later
use strict;
use warnings;
use 5.020;
2022-07-03 10:13:58 +00:00
use utf8;
use DateTime;
use DateTime::Format::Strptime;
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",
'Accept' => 'application/json',
};
$opt{strp1} = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S.000000Z',
time_zone => 'UTC',
);
$opt{strp2} = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %H:%M:%S',
time_zone => 'Europe/Berlin',
);
$opt{strp3} = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S%z',
time_zone => 'Europe/Berlin',
);
return bless( \%opt, $class );
}
sub epoch_to_dt_or_undef {
my ($epoch) = @_;
if ( not $epoch ) {
return undef;
}
return DateTime->from_epoch(
epoch => $epoch,
time_zone => 'Europe/Berlin',
locale => 'de-DE',
);
}
sub parse_datetime {
my ( $self, $dt ) = @_;
return $self->{strp1}->parse_datetime($dt)
// $self->{strp2}->parse_datetime($dt)
// $self->{strp3}->parse_datetime($dt);
}
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'},
'Accept' => 'application/json',
'Authorization' => "Bearer $token",
};
$self->{user_agent}->request_timeout(20)
->get_p(
"https://traewelling.de/api/v1/user/${username}/statuses?limit=1" =>
$header )->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg
= "v1/user/${username}/statuses: HTTP $err->{code} $err->{message}";
$promise->reject( { http => $err->{code}, text => $err_msg } );
return;
}
else {
if ( my $status = $tx->result->json->{data}[0] ) {
my $status_id = $status->{id};
my $message = $status->{body};
my $checkin_at
= $self->parse_datetime( $status->{createdAt} );
my $dep_dt = $self->parse_datetime(
$status->{train}{origin}{departurePlanned} );
my $arr_dt = $self->parse_datetime(
$status->{train}{destination}{arrivalPlanned} );
my $dep_eva
= $status->{train}{origin}{evaIdentifier};
my $arr_eva
= $status->{train}{destination}{evaIdentifier};
my $dep_ds100
= $status->{train}{origin}{rilIdentifier};
my $arr_ds100
= $status->{train}{destination}{rilIdentifier};
my $dep_name
= $status->{train}{origin}{name};
my $arr_name
= $status->{train}{destination}{name};
my $category = $status->{train}{category};
my $linename = $status->{train}{lineName};
Multi-backend support Squashed commit of the following: commit 92518024ba295456358618c0e8180bd8e996fdf1 Author: Birte Kristina Friesel <birte.friesel@uos.de> Date: Fri Jul 26 18:39:46 2024 +0200 add_or_update station: remove superfluos 'new backend id := old backend id' commit df21c20c6e4c86454f8a9ac69121404415217f2a Author: Birte Kristina Friesel <birte.friesel@uos.de> Date: Fri Jul 26 18:35:51 2024 +0200 revert connection targets min_count to 3 commit be335cef07d0b42874f5fc1de4a1d13396e8e807 Author: Birte Kristina Friesel <birte.friesel@uos.de> Date: Fri Jul 26 18:20:05 2024 +0200 mention backend selection in API documentation commit 9f41828fb4f18fd707e0087def3032e8d4c8d7d8 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:19:23 2024 +0200 use_history: not all backends provide route data in departure monitor commit 09714b4d89684b8331d0e96f564a4c7432318f70 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:11:44 2024 +0200 disambiguation: pass correct hafas parameter commit 8cdf1120fc32155dc6525be64601b7c10a9c7f52 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:11:28 2024 +0200 _checked_in: hide Zuglauf link for non-db checkins commit 7455653f541198e0e0a6d11aed421487ffdb6285 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:01:47 2024 +0200 debug output commit b9cda07f85601a58ea32dbdacdd5399f302db52b Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 19:09:07 2024 +0200 fix remaining get_connection_targets / get_connecting_trains_p invocations commit 2759d7258c37c7498905cfe19f6b4c4f6d16bd21 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Wed Jul 24 20:50:12 2024 +0200 support non-DB HAFAS backends (WiP)
2024-07-26 16:55:58 +00:00
my $train_no = $status->{train}{journeyNumber};
my $trip_id = $status->{train}{hafasId};
my ( $train_type, $train_line ) = split( qr{ }, $linename );
$promise->resolve(
{
http => $tx->res->code,
status_id => $status_id,
message => $message,
checkin => $checkin_at,
dep_dt => $dep_dt,
dep_eva => $dep_eva,
dep_ds100 => $dep_ds100,
dep_name => $dep_name,
arr_dt => $arr_dt,
arr_eva => $arr_eva,
arr_ds100 => $arr_ds100,
arr_name => $arr_name,
trip_id => $trip_id,
Multi-backend support Squashed commit of the following: commit 92518024ba295456358618c0e8180bd8e996fdf1 Author: Birte Kristina Friesel <birte.friesel@uos.de> Date: Fri Jul 26 18:39:46 2024 +0200 add_or_update station: remove superfluos 'new backend id := old backend id' commit df21c20c6e4c86454f8a9ac69121404415217f2a Author: Birte Kristina Friesel <birte.friesel@uos.de> Date: Fri Jul 26 18:35:51 2024 +0200 revert connection targets min_count to 3 commit be335cef07d0b42874f5fc1de4a1d13396e8e807 Author: Birte Kristina Friesel <birte.friesel@uos.de> Date: Fri Jul 26 18:20:05 2024 +0200 mention backend selection in API documentation commit 9f41828fb4f18fd707e0087def3032e8d4c8d7d8 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:19:23 2024 +0200 use_history: not all backends provide route data in departure monitor commit 09714b4d89684b8331d0e96f564a4c7432318f70 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:11:44 2024 +0200 disambiguation: pass correct hafas parameter commit 8cdf1120fc32155dc6525be64601b7c10a9c7f52 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:11:28 2024 +0200 _checked_in: hide Zuglauf link for non-db checkins commit 7455653f541198e0e0a6d11aed421487ffdb6285 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 20:01:47 2024 +0200 debug output commit b9cda07f85601a58ea32dbdacdd5399f302db52b Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Thu Jul 25 19:09:07 2024 +0200 fix remaining get_connection_targets / get_connecting_trains_p invocations commit 2759d7258c37c7498905cfe19f6b4c4f6d16bd21 Author: Birte Kristina Friesel <derf@finalrewind.org> Date: Wed Jul 24 20:50:12 2024 +0200 support non-DB HAFAS backends (WiP)
2024-07-26 16:55:58 +00:00
train_no => $train_no,
train_type => $train_type,
line => $linename,
line_no => $train_line,
category => $category,
}
);
return;
}
else {
$promise->reject(
{ text => "v1/${username}/statuses: unknown error" } );
return;
}
}
}
)->catch(
sub {
my ($err) = @_;
$promise->reject( { text => "v1/${username}/statuses: $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'},
'Accept' => 'application/json',
'Authorization' => "Bearer $token",
};
my $promise = Mojo::Promise->new;
$ua->get_p( "https://traewelling.de/api/v1/auth/user" => $header )->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg = "v1/auth/user: HTTP $err->{code} $err->{message}";
$promise->reject($err_msg);
return;
}
else {
my $user_data = $tx->result->json->{data};
$self->{model}->set_user(
uid => $uid,
trwl_id => $user_data->{id},
screen_name => $user_data->{displayName},
user_name => $user_data->{username},
);
$promise->resolve;
return;
}
}
)->catch(
sub {
my ($err) = @_;
$promise->reject("v1/auth/user: $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'},
'Accept' => 'application/json',
'Authorization' => "Bearer $token",
};
my $request = {};
$self->{model}->unlink( uid => $uid );
my $promise = Mojo::Promise->new;
$ua->post_p(
"https://traewelling.de/api/v1/auth/logout" => $header => json =>
$request )->then(
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg
= "v1/auth/logout: HTTP $err->{code} $err->{message}";
$promise->reject($err_msg);
return;
}
else {
$promise->resolve;
return;
}
}
)->catch(
sub {
my ($err) = @_;
$promise->reject("v1/auth/logout: $err");
return;
}
)->wait;
return $promise;
}
sub convert_travelynx_to_traewelling_visibility {
2023-07-13 18:56:30 +00:00
my ($travelynx_visibility) = @_;
my %visibilities = (
2023-07-13 18:56:30 +00:00
# public => StatusVisibility::PUBLIC
100 => 0,
# travelynx => StatusVisibility::AUTHENTICATED
# (only visible for logged in users)
80 => 4,
# followers => StatusVisibility::FOLLOWERS
60 => 2,
# unlisted => StatusVisibility::PRIVATE
# (there is no träwelling equivalent to unlisted, their
# StatusVisibility::UNLISTED shows the journey on the profile)
30 => 3,
# private => StatusVisibility::PRIVATE
10 => 3,
);
return $visibilities{$travelynx_visibility};
}
sub checkin_p {
2020-10-01 17:36:35 +00:00
my ( $self, %opt ) = @_;
my $header = {
'User-Agent' => $self->{header}{'User-Agent'},
'Accept' => 'application/json',
2020-10-01 17:36:35 +00:00
'Authorization' => "Bearer $opt{token}",
};
my $departure_ts = epoch_to_dt_or_undef( $opt{dep_ts} );
my $arrival_ts = epoch_to_dt_or_undef( $opt{arr_ts} );
if ($departure_ts) {
$departure_ts = $departure_ts->rfc3339;
}
if ($arrival_ts) {
$arrival_ts = $arrival_ts->rfc3339;
}
2020-10-01 17:36:35 +00:00
my $request = {
tripId => $opt{trip_id},
lineName => $opt{train_type} . ' '
. ( $opt{train_line} // $opt{train_no} ),
ibnr => \1,
2020-10-01 17:36:35 +00:00
start => q{} . $opt{dep_eva},
destination => q{} . $opt{arr_eva},
departure => $departure_ts,
arrival => $arrival_ts,
toot => $opt{data}{toot} ? \1 : \0,
tweet => $opt{data}{tweet} ? \1 : \0,
2023-07-13 18:56:30 +00:00
visibility =>
convert_travelynx_to_traewelling_visibility( $opt{visibility} )
2020-10-01 17:36:35 +00:00
};
if ( $opt{user_data}{comment} ) {
$request->{body} = $opt{user_data}{comment};
}
my $debug_prefix
= "v1/trains/checkin('$request->{lineName}' $request->{tripId} $request->{start} -> $request->{destination})";
my $promise = Mojo::Promise->new;
2020-10-01 17:36:35 +00:00
$self->{user_agent}->request_timeout(20)
->post_p(
"https://traewelling.de/api/v1/trains/checkin" => $header => json =>
$request )->then(
2020-10-01 17:36:35 +00:00
sub {
my ($tx) = @_;
if ( my $err = $tx->error ) {
my $err_msg = "HTTP $err->{code} $err->{message}";
if ( $tx->res->body ) {
if ( $err->{code} == 409 ) {
my $j = $tx->res->json;
$err_msg .= sprintf(
': Bereits in %s eingecheckt: https://traewelling.de/status/%d',
$j->{message}{lineName},
$j->{message}{status_id}
);
}
else {
$err_msg .= ' ' . $tx->res->body;
}
}
$self->{log}
->debug("Traewelling $debug_prefix error: $err_msg");
2020-10-01 17:36:35 +00:00
$self->{model}->log(
uid => $opt{uid},
2020-10-01 17:36:35 +00:00
message =>
"Konnte $opt{train_type} $opt{train_no} nicht übertragen: $debug_prefix returned $err_msg",
2020-10-01 17:36:35 +00:00
is_error => 1
);
$promise->reject( { http => $err->{code} } );
return;
}
$self->{log}->debug( "... success! " . $tx->res->body );
2020-10-01 17:36:35 +00:00
$self->{model}->log(
uid => $opt{uid},
message => "Eingecheckt in $opt{train_type} $opt{train_no}",
status_id => $tx->res->json->{statusId}
);
$self->{model}->set_latest_push_ts(
uid => $opt{uid},
ts => $opt{checkin_ts}
);
$promise->resolve( { http => $tx->res->code } );
2020-10-01 17:36:35 +00:00
# TODO store status_id in in_transit object so that it can be shown
# on the user status page
return;
}
2020-10-01 17:36:35 +00:00
)->catch(
sub {
my ($err) = @_;
$self->{log}->debug("... $debug_prefix error: $err");
2020-10-01 17:36:35 +00:00
$self->{model}->log(
uid => $opt{uid},
message =>
"Konnte $opt{train_type} $opt{train_no} nicht übertragen: $debug_prefix returned $err",
2020-10-01 17:36:35 +00:00
is_error => 1
);
$promise->reject( { connection => $err } );
return;
2020-10-01 17:36:35 +00:00
}
)->wait;
return $promise;
}
1;