176 lines
6.9 KiB
Perl
176 lines
6.9 KiB
Perl
package DDG::Goodie::Timer;
|
|
# ABSTRACT: Shows a countdown timer
|
|
|
|
use strict;
|
|
use DDG::Goodie;
|
|
|
|
zci answer_type => 'timer';
|
|
zci is_cached => 1;
|
|
|
|
my @triggers = ('timer', 'countdown', 'count down', 'alarm', 'reminder');
|
|
# Triggers that are valid, but not stripped from the resulting query
|
|
my @nonStrippedTriggers = qw(minutes mins seconds secs hours hrs);
|
|
# Triggers that are valid in start only
|
|
my @baseTriggers = qw(start begin set run);
|
|
my @startTriggers = ();
|
|
# creates 'start a', 'begin a'
|
|
map { push(@startTriggers, "$_ a") } @baseTriggers;
|
|
push(@startTriggers, @baseTriggers);
|
|
|
|
# Beautifies the trigger can be appended in front/back of trigger
|
|
my @beautifierTriggers = qw(online);
|
|
#Joins the Timer Value
|
|
my @joiners = qw(for on at to with of);
|
|
# StartEndTriggers to trigger on nonStrippedTriggers, startTriggers, beautifierTriggers and triggers
|
|
my @triggersStartEnd = (@triggers, @nonStrippedTriggers, @startTriggers, @beautifierTriggers);
|
|
# Ambigous triggers which should not give Timer IA
|
|
my @ambigousTriggers = ("20 minutes", "22 minutes", "60 minutes", "48 hours");
|
|
|
|
triggers startend => @triggersStartEnd;
|
|
|
|
my $MAX_TIME = 359999; # 99 hrs 59 mins 59 secs
|
|
|
|
# Get Goodie version for use with image paths
|
|
my $goodieVersion = $DDG::GoodieBundle::OpenSourceDuckDuckGo::VERSION // 999;
|
|
|
|
sub normalize_time_format {
|
|
my $time = shift;
|
|
$time =~ /(?:(?<h>\d{1,2}):)?(?<min>\d{1,2}):(?<sec>\d{2})/;
|
|
my $min_sec = $+{min} . 'min ' . $+{sec} . 'sec';
|
|
return (defined $+{h} ? $+{h} . 'h ' : '') . $min_sec;
|
|
}
|
|
|
|
sub parse_query_for_time {
|
|
my $query = shift;
|
|
$query =~ s/^\s*//;
|
|
$query =~ s/(timer|online)\s*//gi;
|
|
$query =~ s/(?!\a)s/sec/i;
|
|
$query =~ s/(?!\a)m/min/i;
|
|
$query =~ s/(?!\a)h/hrs/i;
|
|
my $timer_re = qr/(?<val>[\d]+\.?[\d]*) ?(?<unit>min|sec|h)/;
|
|
my $time = 0;
|
|
my ($match, $val, $unit);
|
|
$query = normalize_time_format $query if $query =~ /:/;
|
|
while ($query =~ $timer_re) {
|
|
$val = $+{val};
|
|
$unit = $+{unit};
|
|
if ($unit eq 'h') {
|
|
$time += $val * 3600;
|
|
} elsif ($unit eq 'min') {
|
|
$time += $val * 60;
|
|
} elsif ($unit eq 'sec') {
|
|
$time += $val;
|
|
}
|
|
$query =~ s/$timer_re//;
|
|
}
|
|
return ($time <= $MAX_TIME) ? $time : $MAX_TIME;
|
|
}
|
|
|
|
sub build_result {
|
|
my $req = shift;
|
|
my $time = parse_query_for_time($req->query_lc);
|
|
return "$time",
|
|
structured_answer => {
|
|
id => 'timer',
|
|
name => 'Timer',
|
|
signal => 'high',
|
|
meta => {
|
|
sourceName => 'Timer',
|
|
itemType => 'timer',
|
|
},
|
|
data => {
|
|
time => "$time",
|
|
goodie_version => $goodieVersion
|
|
},
|
|
templates => {
|
|
group => 'base',
|
|
detail => 'DDH.timer.timer_wrapper',
|
|
wrap_detail => 'base_detail',
|
|
},
|
|
};
|
|
}
|
|
|
|
handle remainder => sub {
|
|
my $qry = $_;
|
|
my $raw = $req->query_lc;
|
|
my $trgx = join('|', @triggers);
|
|
my $nonStrpTrgx = join('|', @nonStrippedTriggers);
|
|
my $ambTrgx = join('|', @ambigousTriggers);
|
|
my $stTrgx = join('|', @startTriggers);
|
|
my $stTrgxSize = @startTriggers;
|
|
my $btfrTrgx = join('|', @beautifierTriggers);
|
|
my $btfrTrgxSize = @beautifierTriggers;
|
|
my $joinTrgx = join('|', @joiners);
|
|
my $btfrTrigStart = $btfrTrgx.'|'.$stTrgx;
|
|
my $btfrTrigSize = $stTrgxSize+$btfrTrgxSize;
|
|
|
|
# Verify that the trigger words are wrapped with whitespace or that
|
|
# the trigger is at the start or end of the string with white space
|
|
# on either side of it. This prevents triggering on queries such as
|
|
# "countdown.js", "timer.x", "five-alarm", etc
|
|
if($raw !~ /(^|\s)($trgx|$nonStrpTrgx)(\s|$)/i) {
|
|
return;
|
|
}
|
|
|
|
# When ambigous words are present in triggers then it should not
|
|
# invoke Timer IA.
|
|
if($raw =~ /^($ambTrgx)$/){
|
|
return;
|
|
}
|
|
|
|
# When query doesn't ask for timer explicitly and just wants time
|
|
# calculations, don't invoke IA.
|
|
if($raw !~ /($trgx)/ and $raw =~ /[+-]/) {
|
|
return;
|
|
}
|
|
|
|
# When the query is empty and we know that the trigger word matches
|
|
# the trigger exactly (whitespace check) we can return a valid result
|
|
if($qry eq '') {
|
|
return build_result($req);
|
|
}
|
|
|
|
# Trim both sides of the raw query to have the raw regex work
|
|
# properly. Since we need to make sure /^<trigger> for $/ doesn't
|
|
# trigger. Additionally trims down the start triggers
|
|
# <startTriggers> <specific time> <beautifierTriggers> <trigger> ------------- start 10 minute online timer
|
|
# <startTriggers> <trigger> <beautifierTriggers> <specific time> ------------- begin timer online 10 minutes
|
|
# <startTriggers> <trigger> <beautifierTriggers> <joiners> <specific time> --- set timer online for 10 min
|
|
$raw =~ s/^\s*(\b(\s*($btfrTrigStart)\s*)\b){1,$btfrTrigSize}\s*//;
|
|
$raw =~ s/($btfrTrgx)$//;
|
|
|
|
# Parse the raw query to remove common terms. This allows us to
|
|
# catch the more specific queries and handle them. We also strip
|
|
# the extra bounding whitespace. The trigger is
|
|
# set to startend, causing "online timer for ..." to trigger.
|
|
#
|
|
#
|
|
# Note: Beautifiers surrounds the triggers and joiners connect it to the value
|
|
#
|
|
# Matches on...
|
|
# <specific time> <beautifierTriggers> <trigger> ------------------------------ 10 minute online timer
|
|
# <trigger> <beautifierTriggers> <specific time> ------------------------------ timer online 10 minutes
|
|
# <trigger> <beautifierTriggers> <joiners> <specific time> -------------------- timer online for 10 min
|
|
# <specific time> <trigger> <beautifierTriggers> ------------------------------ 10 minute timer online
|
|
# <startTriggers> <beautifierTriggers> <trigger> <specific time> ------------- online timer 10 minutes
|
|
# <beautifierTriggers> <trigger> <joiners> <specific time> -------------------- online timer for 10 min
|
|
# <specific time> <beautifierTriggers> <trigger> ------------------------------ 10 minute online countdown timer
|
|
# <trigger> <beautifierTriggers> <specific time> ------------------------------ timer alarm online 10 minutes
|
|
# <trigger> <beautifierTriggers> <joiners> <specific time> -------------------- alarm timer online at 10 min
|
|
# <specific time> <trigger> <beautifierTriggers> ------------------------------ 10 minute countdown timer online
|
|
# <startTriggers> <beautifierTriggers> <trigger> <specific time> ------------- online countdown alarm 10 minutes
|
|
# <beautifierTriggers> <trigger> <joiners> <specific time> -------------------- online timer with 10 min
|
|
$raw =~ s/($btfrTrgx\s*)?(\b(\s*($trgx)\s*)\b)($btfrTrgx)?\s*($joinTrgx)?//ig;
|
|
|
|
if($raw eq '') {
|
|
return build_result($req);
|
|
}elsif($raw =~ /^(\s?([\d.]+ ?(m(in((ute)?s?)?)?|s(ec((ond)?s?)?)?|h(ours?)?|hr))\s?)+$/) {
|
|
return build_result($req);
|
|
}elsif($raw =~ /^( ?((\d{1,2}:)?\d{1,2}:\d{2}) ?)/) {
|
|
return build_result($req);
|
|
}
|
|
return;
|
|
};
|
|
|
|
1;
|