File Coverage

blib/lib/Convos/Plugin/Helpers.pm
Criterion Covered Total %
statement 47 173 27.1
branch 3 42 7.1
condition 6 41 14.6
subroutine 10 35 28.5
pod 10 10 100.0
total 76 301 25.2


line stmt bran cond sub pod time code
1             package Convos::Plugin::Helpers;
2              
3             =head1 NAME
4              
5             Convos::Plugin::Helpers - Mojo's little helpers
6              
7             =cut
8              
9 36     36   29687 use Mojo::Base 'Mojolicious::Plugin';
  36         63  
  36         221  
10 36     36   4602 use Mojo::JSON 'j';
  36         49  
  36         1811  
11 36     36   175 use Convos::Core::Util qw( format_time id_as );
  36         49  
  36         1723  
12 36     36   17804 use URI::Find;
  36         214679  
  36         2451  
13 36   50 36   234 use constant DEBUG => $ENV{CONVOS_DEBUG} || 0;
  36         72  
  36         2644  
14 36   50 36   175 use constant DEFAULT_URL => $ENV{DEFAULT_AVATAR_URL} || 'https://graph.facebook.com/%s/picture?height=40&width=40';
  36         52  
  36         1932  
15 36   50 36   159 use constant GRAVATAR_URL => $ENV{GRAVATAR_AVATAR_URL} || 'https://gravatar.com/avatar/%s?s=40&d=retro';
  36         52  
  36         1892  
16 36   50 36   155 use constant DISABLE_AUTO_EMBED => $ENV{CONVOS_DISABLE_AUTO_EMBED} || 0;
  36         52  
  36         78241  
17              
18             =head1 HELPERS
19              
20             =head2 active_class
21              
22             Will add "active" class to a link based on url
23              
24             =cut
25              
26             sub active_class {
27 0     0 1 0 my $c = shift;
28 0         0 my $url = $c->url_for(@_);
29              
30 0 0       0 return ($url, $url eq $c->req->url->path ? (class => 'active') : (),);
31             }
32              
33             =head2 id_as
34              
35             See L.
36              
37             =head2 as_id
38              
39             See L.
40              
41             =head2 conversation_list
42              
43             Will render the conversation list for all conversations.
44              
45             =cut
46              
47             sub conversation_list {
48 0     0 1 0 my ($self, $cb) = @_;
49 0         0 my $login = $self->session('login');
50              
51             $self->delay(
52             sub {
53 0     0   0 my ($delay) = @_;
54 0         0 $self->redis->zrevrange("user:$login:conversations", 0, -1, 'WITHSCORES', $delay->begin);
55 0         0 $self->redis->smembers("user:$login:connections", $delay->begin);
56             },
57             sub {
58 0     0   0 my ($delay, $conversation_list, $networks) = @_;
59 0         0 my $i = 0;
60              
61 0         0 while ($i < @$conversation_list) {
62 0         0 my $name = $conversation_list->[$i];
63 0         0 my $timestamp = splice @$conversation_list, ($i + 1), 1;
64 0         0 my ($network, $target) = id_as $name;
65              
66 0   0     0 $target ||= '';
67 0 0       0 $conversation_list->[$i]
68             = {network => $network, is_channel => $target =~ /^[#&]/ ? 1 : 0, target => $target, timestamp => $timestamp,
69             };
70              
71 0         0 $self->redis->zcount("user:$login:connection:$network:$target:msg", $timestamp, '+inf', $delay->begin);
72 0         0 $i++;
73             }
74              
75 0         0 $delay->begin->(undef, $conversation_list);
76 0   0     0 $self->stash(conversation_list => $conversation_list, networks => $networks || []);
77             },
78             sub {
79 0     0   0 my ($delay, @args) = @_;
80 0         0 my $conversation_list = pop @args;
81              
82 0         0 for my $c (@$conversation_list) {
83 0   0     0 $c->{unread} = shift @args || 0;
84             }
85              
86 0 0       0 return $self->$cb($conversation_list) if $cb;
87 0         0 return $self->render('client/conversation_list');
88             },
89 0         0 );
90             }
91              
92             =head2 day_changed
93              
94             $bool = $c->day_changed($epoch_now, $epoch_prev);
95              
96             Returns true if day has changed between two events.
97              
98             =cut
99              
100             sub day_changed {
101 0     0 1 0 my $c = shift;
102 0 0       0 my $epoch_now = shift or return 0;
103 0 0       0 my $epoch_prev = shift or return 0;
104 0         0 my $today = int($epoch_now - $epoch_now % 86400 + 86400);
105 0         0 my $yesterday = int($epoch_prev - $epoch_prev % 86400 + 86400);
106              
107 0         0 return $yesterday < $today;
108             }
109              
110             =head2 format_conversation
111              
112             $c->format_conversation(\&iterator, \&callback);
113              
114             Takes a list of JSON strings and turns them into a list of hash-refs where the
115             "message" key contains a formatted version with HTML and such. The result will
116             be passed on to the C<$callback>.
117              
118             =cut
119              
120             sub format_conversation {
121 0     0 1 0 my ($c, $conversation, $cb) = @_;
122 0         0 my $delay = Mojo::IOLoop->delay;
123              
124 0         0 while (my $message = $conversation->()) {
125 0         0 $message->{embed} = '';
126 0   0     0 $message->{uuid} ||= '';
127 0   0     0 $message->{avatar} ||= '';
128              
129 0 0       0 defined $message->{message} and _parse_message($c, $message, $delay);
130 0 0       0 defined $message->{user} and _add_avatar($c, $message, $delay);
131 0         0 push @{$c->{conversation}}, $message;
  0         0  
132             }
133              
134 0         0 $c->render_later;
135 0         0 $c->{format_conversation}++;
136              
137             $delay->once(
138             finish => sub {
139 0 0 0 0   0 $c->$cb(delete $c->{conversation} || []) unless --$c->{format_conversation};
140             }
141 0         0 );
142              
143 0         0 $delay->begin->(); # need to do at least one step
144             }
145              
146             sub _add_avatar {
147 0     0   0 my ($c, $message, $delay) = @_;
148 0   0     0 my $cache = $c->stash->{'convos.avatar_cache'} ||= {};
149 0         0 my $user = $message->{user};
150              
151 0         0 $user =~ s!^~!!; # somenick!~someuser@1.2.3.4
152              
153 0 0       0 if ($cache->{$user}) {
154 0         0 return $message->{avatar} = $cache->{$user};
155             }
156              
157 0         0 my $cb = $delay->begin;
158             $c->redis->hmget(
159             "user:$user",
160             qw( avatar email ),
161             sub {
162 0     0   0 my ($redis, $data) = @_;
163 0   0     0 my $id = shift @$data || shift @$data || "$user\@$message->{host}";
164              
165 0 0       0 if ($id =~ /\@/) {
    0          
166 0         0 $message->{avatar} = sprintf GRAVATAR_URL, Mojo::Util::md5_sum($id);
167             }
168             elsif ($id) {
169 0         0 $message->{avatar} = sprintf DEFAULT_URL, $id;
170             }
171              
172 0         0 $cache->{$user} = $message->{avatar};
173 0         0 $cb->();
174             }
175 0         0 );
176             }
177              
178             sub _parse_message {
179 0     0   0 my ($c, $message, $delay) = @_;
180 0         0 my @class = DISABLE_AUTO_EMBED ? () : (class => 'external');
181              
182             # http://www.mirc.com/colors.html
183 0         0 $message->{message} =~ s/\x03\d{0,15}(,\d{0,15})?//g;
184 0         0 $message->{message} =~ s/[\x00-\x1f]//g;
185              
186 0   0     0 $message->{highlight} ||= 0;
187 0         0 $message->{message} = Mojo::Util::xml_escape($message->{message});
188              
189             URI::Find->new(
190             sub {
191 0     0   0 my $url = Mojo::Util::html_unescape(shift . '');
192 0         0 $c->link_to($url, $url, target => '_blank', @class);
193             }
194 0         0 )->find(\$message->{message});
195             }
196              
197             =head2 logf
198              
199             See L.
200              
201             =head2 redis
202              
203             Returns a L object.
204              
205             =cut
206              
207             sub redis {
208 41     41 1 10114 my $c = shift;
209 41 50   0   405 my $cache_to = $c->{tx} ? 'stash' : sub { $c->app->defaults };
  0         0  
210              
211 41   66     269 $c->$cache_to->{redis} ||= do {
212 41         1185 my $log = $c->app->log;
213 41 100       1433 my $url = $ENV{CONVOS_REDIS_URL} or die "CONVOS_REDIS_URL is not set. Run 'perldoc Convos' for details.\n";
214 6         39 my $redis = Mojo::Redis->new(server => $url);
215              
216             $redis->on(
217             error => sub {
218 0     0   0 $log->error("[REDIS ERROR] $_[1]");
219             }
220 6         81 );
221              
222 6         231 $redis;
223             };
224             }
225              
226             =head2 notification_list
227              
228             Will render notifications.
229              
230             =cut
231              
232             sub notification_list {
233 0     0 1 0 my ($self, $cb) = @_;
234 0         0 my $login = $self->session('login');
235 0         0 my $key = "user:$login:notifications";
236              
237             $self->delay(
238             sub {
239 0     0   0 my ($delay) = @_;
240 0         0 $self->redis->lrange($key, 0, 20, $delay->begin);
241             },
242             sub {
243 0     0   0 my ($delay, $notification_list) = @_;
244 0         0 my $n_notifications = 0;
245 0         0 my %nick_seen = ();
246 0         0 my $i = 0;
247              
248 0         0 while ($i < @$notification_list) {
249 0         0 my $n = j $notification_list->[$i];
250 0         0 $n->{index} = $i;
251 0 0       0 $n->{is_channel} = $n->{target} =~ /^[#&]/ ? 1 : 0;
252              
253 0 0 0     0 if (!$n->{is_channel} and $nick_seen{$n->{target}}++) {
254 0         0 $self->redis->lrem($key, 1, $notification_list->[$i]);
255 0         0 splice @$notification_list, $i, 1, ();
256 0         0 next;
257             }
258              
259 0         0 $notification_list->[$i] = $n;
260 0 0       0 $n_notifications++ unless $n->{read};
261 0         0 $i++;
262             }
263              
264 0         0 $self->stash(notification_list => $notification_list, n_notifications => $n_notifications);
265 0         0 $self->$cb($notification_list);
266             },
267 0         0 );
268             }
269              
270             =head2 send_partial
271              
272             Will render "partial" and L the result.
273              
274             =cut
275              
276             sub send_partial {
277 0     0 1 0 my $c = shift;
278              
279 0 0       0 eval { $c->send($c->render_to_string(@_)->to_string) } or $c->app->log->error($@);
  0         0  
280             }
281              
282             =head2 timestamp
283              
284             Returns a "Ediv>" tag with a timestamp.
285              
286             =cut
287              
288             sub timestamp {
289 0     0 1 0 my ($c, $timestamp) = @_;
290 0   0     0 my $offset = $c->session('tz_offset') || 0;
291 0         0 my $now = time;
292 0         0 my $format = '%e. %b %H:%M';
293              
294 0   0     0 $timestamp ||= $now;
295 0 0       0 $format = '%H:%M' if $timestamp > $now - 86400;
296 0         0 $timestamp += $offset * 3600; # offset is in hours
297              
298 0         0 $c->tag(
299             'div',
300             class => 'timestamp',
301             title => format_time($timestamp, '%e. %B %H:%M:%S'),
302             format_time($timestamp, $format),
303             );
304             }
305              
306             =head2 redirect_last
307              
308             Redirect to the last visited channel for $login. Falls back to settings.
309              
310             =cut
311              
312             sub redirect_last {
313 0     0 1 0 my ($self, $login) = @_;
314 0         0 my $redis = $self->redis;
315              
316             $self->delay(
317             sub {
318 0     0   0 my ($delay) = @_;
319 0         0 $redis->zrevrange("user:$login:conversations", 0, 1, $delay->begin);
320 0         0 $redis->srandmember("user:$login:connections", $delay->begin);
321             },
322             sub {
323 0     0   0 my ($delay, $names, $network) = @_;
324              
325 0 0 0     0 if ($names and $names->[0]) {
    0          
326 0 0       0 if (my ($network, $target) = id_as $names->[0]) {
327 0         0 return $self->redirect_to('view', network => $network, target => $target);
328             }
329             }
330             elsif ($network) {
331 0         0 return $self->redirect_to('view.network', network => $network);
332             }
333              
334 0         0 $self->redirect_to('wizard');
335             },
336 0         0 );
337             }
338              
339             =head1 METHODS
340              
341             =head2 register
342              
343             Will register the L above.
344              
345             =cut
346              
347             sub register {
348 45     45 1 5316 my ($self, $app, $config) = @_;
349              
350 45         240 $app->helper(active_class => \&active_class);
351 45         3591 $app->helper(format_conversation => \&format_conversation);
352 45         2712 $app->helper(connection_list => \&connection_list);
353 45         2929 $app->helper(conversation_list => \&conversation_list);
354 45         2588 $app->helper(day_changed => \&day_changed);
355 45         2633 $app->helper(logf => \&Convos::Core::Util::logf);
356 45     0   2680 $app->helper(format_time => sub { shift; format_time(@_); });
  0         0  
  0         0  
357 45         2595 $app->helper(notification_list => \¬ification_list);
358 45         2601 $app->helper(redis => \&redis);
359 45     0   2658 $app->helper(as_id => sub { shift; Convos::Core::Util::as_id(@_) });
  0         0  
  0         0  
360 45     0   2556 $app->helper(id_as => sub { shift; Convos::Core::Util::id_as(@_) });
  0         0  
  0         0  
361 45         2729 $app->helper(send_partial => \&send_partial);
362 45         2559 $app->helper(timestamp => \×tamp);
363 45         2516 $app->helper(redirect_last => \&redirect_last);
364             }
365              
366             =head1 AUTHOR
367              
368             Jan Henning Thorsen - C
369              
370             =cut
371              
372             1;