#!/usr/bin/env perl use strict; use warnings; use File::Basename; use HTTP::Tiny; use JSON::PP qw; my @REQUIRED_CONFIG_KEYS = qw(url user token); sub usage { die < wait randomly from 0 to before doing anything -n | --dry-run fetch data but don't change anything -v | --verbose increase log level Sample config file (~/.config/culosillad/fulanito.conf): url = https://time.example.com user = fulanito token = 123456789 EOF ; } # Parse CLI arguments my $DRYRUN = my $CRON = my $VERBOSE = 0; my $MODE; while (my $opt = shift) { if ($opt eq '--cron') { $CRON = int(shift) || die "Invalid argument for '--cron'\n"; } elsif ($opt eq '-v' || $opt eq '--verbose') { $VERBOSE = 1; } elsif ($opt eq '-n' || $opt eq '--dryrun') { $DRYRUN = 1; } elsif ($opt eq 'start' || $opt eq 'stop') { $MODE = $opt; } } usage unless $MODE; my $CONFDIR = $ENV{'CULOSILLAD_CONFDIR'} || ($ENV{'XDG_CONFIG_HOME'} || glob '~/.config') . '/culosillad'; mkdir $CONFDIR; print "[+] Configs dir: $CONFDIR\n" if $VERBOSE == 1; my @CONFIGS = glob("$CONFDIR/*"); sub read_config { my $cfgfile = shift; my %config; print "[+] Reading config $cfgfile\n" if $VERBOSE == 1; open(my $fh, '<', $cfgfile) or die "Could not open file: $!"; my $line = 1; while (<$fh>) { if (/^([a-z]+)\s*=\s*(\S+)$/) { $config{$1} = $2; } else { die "Error reading config $cfgfile (line=$line)\n" } $line += 1; } foreach my $key (@REQUIRED_CONFIG_KEYS) { die "Missing key '$key' in config file\n" unless exists $config{$key}; } return \%config; } my %CONFIG; sub request { my ($method, $endpoint) = @_; my $response = HTTP::Tiny->new( agent => 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0' )->request( $method, "$CONFIG{url}/api/$endpoint", { headers => { 'X-AUTH-USER' => $CONFIG{user}, 'X-AUTH-TOKEN' => $CONFIG{token}, } } ); my $content = decode_json($response->{content}); $response->{status} eq '200' or die "Error calling '$endpoint': '$content->{message}'\n"; return $content; } sub get_recent_timesheets { return request('GET', 'timesheets/recent'); } sub get_active_timesheets { return request('GET', 'timesheets/active'); } sub get_active_ets { my $timesheets = get_active_timesheets(); return grep { $_->{project}{name} eq "Fichaje_Empleados" } @$timesheets; } sub get_ets { my $timesheets = get_recent_timesheets(); return grep { $_->{project}{name} eq "Fichaje_Empleados" } @$timesheets; } sub start_ets { my $tsid = shift; return request('PATCH', "timesheets/$tsid/restart"); } sub stop_ets { my $tsid = shift; return request('PATCH', "timesheets/$tsid/stop"); } print "[+] DRY RUN: not doing anything\n" if $DRYRUN == 1; if ($CRON > 0) { my $minutes = int(rand($CRON)); printf "[+] Cron mode enabled (max: %s minutes); sleeping %s minutes...\n", $CRON, $minutes; sleep $minutes * 60; } foreach my $cfgfile (@CONFIGS) { next unless -f $cfgfile; # skip directories %CONFIG = %{ read_config($cfgfile) }; my ($ets) = get_active_ets(); if ($MODE eq 'start') { die "Refusing to start; found active ETS ($ets->{id})\n" if defined $ets; my ($ets) = get_ets(); die "Unable to find ETS\n" unless defined $ets; start_ets($ets->{id}) unless $DRYRUN == 1; print "[+] OK! Started successfully\n"; } elsif ($MODE eq 'stop') { die "Unable to find active timesheet\n" unless defined $ets; stop_ets($ets->{id}) unless $DRYRUN == 1; print "[+] OK! Stopped successfully\n"; } }