line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package BusyBird::Main::PSGI::View; |
2
|
7
|
|
|
7
|
|
79672
|
use v5.8.0; |
|
7
|
|
|
|
|
19
|
|
|
7
|
|
|
|
|
293
|
|
3
|
7
|
|
|
7
|
|
32
|
use strict; |
|
7
|
|
|
|
|
25
|
|
|
7
|
|
|
|
|
221
|
|
4
|
7
|
|
|
7
|
|
123
|
use warnings; |
|
7
|
|
|
|
|
11
|
|
|
7
|
|
|
|
|
228
|
|
5
|
7
|
|
|
7
|
|
37
|
use BusyBird::Util qw(set_param split_with_entities); |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
385
|
|
6
|
7
|
|
|
7
|
|
139
|
use Carp; |
|
7
|
|
|
|
|
14
|
|
|
7
|
|
|
|
|
415
|
|
7
|
7
|
|
|
7
|
|
37
|
use Try::Tiny; |
|
7
|
|
|
|
|
19
|
|
|
7
|
|
|
|
|
425
|
|
8
|
7
|
|
|
7
|
|
39
|
use Scalar::Util qw(weaken); |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
365
|
|
9
|
7
|
|
|
7
|
|
41
|
use JSON qw(to_json); |
|
7
|
|
|
|
|
11
|
|
|
7
|
|
|
|
|
57
|
|
10
|
7
|
|
|
7
|
|
5478
|
use Text::Xslate qw(html_builder html_escape); |
|
7
|
|
|
|
|
71852
|
|
|
7
|
|
|
|
|
685
|
|
11
|
7
|
|
|
7
|
|
74
|
use File::Spec; |
|
7
|
|
|
|
|
12
|
|
|
7
|
|
|
|
|
145
|
|
12
|
7
|
|
|
7
|
|
42
|
use Encode (); |
|
7
|
|
|
|
|
15
|
|
|
7
|
|
|
|
|
105
|
|
13
|
7
|
|
|
7
|
|
3935
|
use JavaScript::Value::Escape (); |
|
7
|
|
|
|
|
4280
|
|
|
7
|
|
|
|
|
188
|
|
14
|
7
|
|
|
7
|
|
42
|
use DateTime::TimeZone; |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
162
|
|
15
|
7
|
|
|
7
|
|
29
|
use BusyBird::DateTime::Format; |
|
7
|
|
|
|
|
12
|
|
|
7
|
|
|
|
|
136
|
|
16
|
7
|
|
|
7
|
|
28
|
use BusyBird::Log qw(bblog); |
|
7
|
|
|
|
|
8
|
|
|
7
|
|
|
|
|
346
|
|
17
|
7
|
|
|
7
|
|
38
|
use BusyBird::SafeData qw(safed); |
|
7
|
|
|
|
|
11
|
|
|
7
|
|
|
|
|
353
|
|
18
|
7
|
|
|
7
|
|
3552
|
use Cache::Memory::Simple; |
|
7
|
|
|
|
|
4817
|
|
|
7
|
|
|
|
|
194
|
|
19
|
7
|
|
|
7
|
|
3614
|
use Plack::Util (); |
|
7
|
|
|
|
|
65631
|
|
|
7
|
|
|
|
|
226
|
|
20
|
7
|
|
|
7
|
|
72
|
use Tie::IxHash; |
|
7
|
|
|
|
|
14
|
|
|
7
|
|
|
|
|
27031
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub new { |
23
|
53
|
|
|
53
|
1
|
367
|
my ($class, %args) = @_; |
24
|
53
|
|
|
|
|
277
|
my $self = bless { |
25
|
|
|
|
|
|
|
main_obj => undef, |
26
|
|
|
|
|
|
|
renderer => undef, |
27
|
|
|
|
|
|
|
}, $class; |
28
|
53
|
|
|
|
|
313
|
$self->set_param(\%args, "main_obj", undef, 1); |
29
|
53
|
|
|
|
|
214
|
$self->set_param(\%args, "script_name", undef, 1); |
30
|
53
|
|
|
|
|
281
|
my $sharedir = $self->{main_obj}->get_config('sharedir_path'); |
31
|
53
|
|
|
|
|
271
|
$sharedir =~ s{/+$}{}; |
32
|
|
|
|
|
|
|
$self->{renderer} = Text::Xslate->new( |
33
|
|
|
|
|
|
|
path => [ File::Spec->catdir($sharedir, 'www', 'templates') ], |
34
|
|
|
|
|
|
|
cache_dir => File::Spec->tmpdir, |
35
|
|
|
|
|
|
|
syntax => 'Kolon', |
36
|
|
|
|
|
|
|
function => $self->template_functions(), |
37
|
|
|
|
|
|
|
warn_handler => sub { |
38
|
0
|
|
|
0
|
|
0
|
bblog("warn", @_); |
39
|
|
|
|
|
|
|
}, |
40
|
|
|
|
|
|
|
## we don't use die_handler because (1) it is called for every |
41
|
|
|
|
|
|
|
## death even when it's in eval() scope, (2) exceptions are |
42
|
|
|
|
|
|
|
## caught by Xslate and passed to warn_handler anyway. |
43
|
53
|
|
|
|
|
1855
|
); |
44
|
53
|
|
|
|
|
16322
|
return $self; |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub response_notfound { |
48
|
15
|
|
|
15
|
1
|
391
|
my ($self, $message) = @_; |
49
|
15
|
|
100
|
|
|
67
|
$message ||= 'Not Found'; |
50
|
15
|
|
|
|
|
187
|
return ['404', |
51
|
|
|
|
|
|
|
['Content-Type' => 'text/plain', |
52
|
|
|
|
|
|
|
'Content-Length' => length($message)], |
53
|
|
|
|
|
|
|
[$message]]; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub response_error_html { |
57
|
9
|
|
|
9
|
1
|
3345
|
my ($self, $http_code, $message) = @_; |
58
|
9
|
|
|
|
|
55
|
return $self->_response_template( |
59
|
|
|
|
|
|
|
template => 'error.tx', args => {error => $message}, |
60
|
|
|
|
|
|
|
code => $http_code |
61
|
|
|
|
|
|
|
); |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
sub response_json { |
66
|
147
|
|
|
147
|
1
|
293
|
my ($self, $res_code, $response_object) = @_; |
67
|
|
|
|
|
|
|
my $message = try { |
68
|
147
|
50
|
|
147
|
|
4143
|
die "response must not be undef" if not defined $response_object; |
69
|
147
|
100
|
100
|
|
|
1525
|
if($res_code eq '200' && ref($response_object) eq "HASH" && !exists($response_object->{error})) { |
|
|
|
100
|
|
|
|
|
70
|
74
|
|
|
|
|
218
|
$response_object->{error} = undef; |
71
|
|
|
|
|
|
|
} |
72
|
147
|
|
|
|
|
814
|
to_json($response_object, {ascii => 1}) |
73
|
|
|
|
|
|
|
}catch { |
74
|
|
|
|
|
|
|
undef |
75
|
147
|
|
|
1
|
|
1073
|
}; |
|
1
|
|
|
|
|
32
|
|
76
|
147
|
100
|
|
|
|
7300
|
if(defined($message)) { |
77
|
|
|
|
|
|
|
return [ |
78
|
146
|
|
|
|
|
1118
|
$res_code, ['Content-Type' => 'application/json; charset=utf-8'], |
79
|
|
|
|
|
|
|
[$message] |
80
|
|
|
|
|
|
|
]; |
81
|
|
|
|
|
|
|
}else { |
82
|
1
|
|
|
|
|
5
|
return $self->response_json(500, {error => "error while encoding to JSON."}); |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
sub _response_template { |
87
|
36
|
|
|
36
|
|
141
|
my ($self, %args) = @_; |
88
|
36
|
|
|
|
|
100
|
my $template_name = delete $args{template}; |
89
|
36
|
50
|
|
|
|
119
|
croak 'template parameter is mandatory' if not defined $template_name; |
90
|
36
|
|
|
|
|
72
|
my $args = delete $args{args}; |
91
|
36
|
|
100
|
|
|
191
|
my $code = delete $args{code} || 200; |
92
|
36
|
|
50
|
|
|
185
|
my $headers = delete $args{headers} || []; |
93
|
36
|
50
|
|
|
|
187
|
if(!Plack::Util::header_exists($headers, 'Content-Type')) { |
94
|
36
|
|
|
|
|
973
|
push(@$headers, 'Content-Type', 'text/html; charset=utf8'); |
95
|
|
|
|
|
|
|
} |
96
|
36
|
|
|
|
|
2229
|
my $ret = Encode::encode('utf8', $self->{renderer}->render($template_name, $args)); |
97
|
36
|
|
|
|
|
4374
|
return [$code, $headers, [$ret]]; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
my $REGEXP_URL_CHAR = qr{[A-Za-z0-9~\/._!\?\&=\-%#\+:\;,\@\']}; |
102
|
|
|
|
|
|
|
my $REGEXP_HTTP_URL = qr{https?:\/\/$REGEXP_URL_CHAR+}; |
103
|
|
|
|
|
|
|
my $REGEXP_ABSOLUTE_PATH = qr{/$REGEXP_URL_CHAR*}; |
104
|
|
|
|
|
|
|
my %URL_ATTRIBUTES = map { $_ => 1 } qw(src href); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
sub _is_valid_link_url { |
107
|
107
|
|
|
107
|
|
135
|
my ($url) = @_; |
108
|
107
|
100
|
|
|
|
219
|
$url = "" if not defined $url; |
109
|
107
|
|
|
|
|
5137
|
return ($url =~ /^(?:$REGEXP_HTTP_URL|$REGEXP_ABSOLUTE_PATH)$/); |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
sub _html_attributes_string { |
113
|
108
|
|
|
108
|
|
244
|
my ($mandatory_attrs_ref, @attr) = @_; |
114
|
108
|
|
|
|
|
303
|
for(my $i = 0 ; $i < $#attr ; $i += 2) { |
115
|
224
|
|
|
|
|
607
|
$attr[$i] = lc($attr[$i]); |
116
|
|
|
|
|
|
|
} |
117
|
108
|
|
|
|
|
754
|
tie(my %attr, 'Tie::IxHash', @attr); |
118
|
108
|
|
|
|
|
3570
|
foreach my $attr_key (@$mandatory_attrs_ref) { |
119
|
108
|
100
|
|
|
|
417
|
croak "$attr_key attribute is mandatory" if not defined $attr{$attr_key}; |
120
|
|
|
|
|
|
|
} |
121
|
55
|
|
|
|
|
516
|
my @attr_strings = (); |
122
|
55
|
|
|
|
|
174
|
foreach my $attr_key (keys %attr) { |
123
|
73
|
|
|
|
|
962
|
my $attr_value = $attr{$attr_key}; |
124
|
73
|
|
|
|
|
458
|
my $value_str; |
125
|
73
|
100
|
|
|
|
215
|
if($URL_ATTRIBUTES{$attr_key}) { |
126
|
55
|
100
|
|
|
|
97
|
croak "$attr_key attribute is invalid as a URL" if not _is_valid_link_url($attr_value); |
127
|
24
|
|
|
|
|
33
|
$value_str = $attr_value; |
128
|
|
|
|
|
|
|
}else { |
129
|
18
|
|
|
|
|
47
|
$value_str = html_escape($attr_value); |
130
|
|
|
|
|
|
|
} |
131
|
42
|
|
|
|
|
302
|
push(@attr_strings, html_escape($attr_key) . qq{="$value_str"}); |
132
|
|
|
|
|
|
|
} |
133
|
24
|
|
|
|
|
140
|
return join(" ", @attr_strings); |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub _html_link { |
137
|
73
|
|
|
73
|
|
4206
|
my ($text, @attr) = @_; |
138
|
73
|
100
|
|
|
|
174
|
$text = "" if not defined $text; |
139
|
73
|
|
|
|
|
248
|
my $escaped_text = html_escape($text); |
140
|
|
|
|
|
|
|
return try { |
141
|
73
|
|
|
73
|
|
2162
|
my $attr_str = _html_attributes_string(['href'], @attr); |
142
|
19
|
|
|
|
|
100
|
return qq{$escaped_text}; |
143
|
|
|
|
|
|
|
}catch { |
144
|
54
|
|
|
54
|
|
5849
|
return $escaped_text; |
145
|
73
|
|
|
|
|
496
|
}; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub _html_link_status_text { |
149
|
13
|
|
|
13
|
|
19
|
my ($text, $url) = @_; |
150
|
13
|
|
|
|
|
21
|
return _html_link($text, href => $url, target => "_blank"); |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
sub _make_path { |
154
|
488
|
|
|
488
|
|
498
|
my ($script_name, $given_path) = @_; |
155
|
488
|
100
|
|
|
|
973
|
if(substr($given_path, 0, 1) eq "/") { |
156
|
486
|
|
|
|
|
2841
|
return "$script_name$given_path"; |
157
|
|
|
|
|
|
|
}else { |
158
|
2
|
|
|
|
|
11
|
return $given_path; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
sub template_functions { |
163
|
54
|
|
|
54
|
1
|
230
|
my $script_name = $_[0]->{script_name}; |
164
|
|
|
|
|
|
|
return { |
165
|
|
|
|
|
|
|
js => \&JavaScript::Value::Escape::js, |
166
|
|
|
|
|
|
|
link => html_builder(\&_html_link), |
167
|
|
|
|
|
|
|
image => html_builder { |
168
|
35
|
|
|
35
|
|
14697
|
my (@attr) = @_; |
169
|
|
|
|
|
|
|
return try { |
170
|
35
|
|
|
|
|
1422
|
my $attr_str = _html_attributes_string(['src'], @attr); |
171
|
5
|
|
|
|
|
18
|
return qq{} |
172
|
|
|
|
|
|
|
}catch { |
173
|
30
|
|
|
|
|
4554
|
return ""; |
174
|
35
|
|
|
|
|
380
|
}; |
175
|
|
|
|
|
|
|
}, |
176
|
|
|
|
|
|
|
bb_level => sub { |
177
|
54
|
|
|
54
|
|
21342
|
my $level = shift; |
178
|
54
|
100
|
|
|
|
163
|
$level = 0 if not defined $level; |
179
|
54
|
|
|
|
|
257
|
return $level; |
180
|
|
|
|
|
|
|
}, |
181
|
322
|
|
|
322
|
|
28332
|
path => sub { _make_path($script_name, $_[0]) }, |
182
|
55
|
|
|
55
|
|
1624
|
script_name => sub { $script_name }, |
183
|
54
|
|
|
|
|
409
|
}; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub _render_text_segment { |
187
|
24
|
|
|
24
|
|
37
|
my ($self, $timeline_name, $segment, $status) = @_; |
188
|
24
|
100
|
66
|
|
|
106
|
return undef if !defined($segment->{entity}) || !defined($segment->{type}); |
189
|
10
|
|
|
|
|
43
|
my $url_builder = $self->{main_obj}->get_timeline_config($timeline_name, "$segment->{type}_entity_url_builder"); |
190
|
10
|
|
|
|
|
34
|
my $text_builder = $self->{main_obj}->get_timeline_config($timeline_name, "$segment->{type}_entity_text_builder"); |
191
|
10
|
50
|
33
|
|
|
42
|
return undef if !defined($url_builder) || !defined($text_builder); |
192
|
10
|
|
|
|
|
23
|
my $url_str = $url_builder->($segment->{text}, $segment->{entity}, $status); |
193
|
10
|
50
|
|
|
|
22
|
return undef if !_is_valid_link_url($url_str); |
194
|
10
|
|
|
|
|
31
|
my $text_str = $text_builder->($segment->{text}, $segment->{entity}, $status); |
195
|
10
|
50
|
|
|
|
26
|
$text_str = "" if not defined $text_str; |
196
|
10
|
|
|
|
|
19
|
return _html_link_status_text($text_str, $url_str); |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
sub template_functions_for_timeline { |
200
|
27
|
|
|
27
|
1
|
50
|
my ($self, $timeline_name) = @_; |
201
|
27
|
|
|
|
|
94
|
weaken $self; ## in case the functions are kept by $self |
202
|
|
|
|
|
|
|
return { |
203
|
|
|
|
|
|
|
bb_timestamp => sub { |
204
|
28
|
|
|
28
|
|
1709
|
my ($timestamp_string) = @_; |
205
|
28
|
100
|
|
|
|
144
|
return "" if !$timestamp_string; |
206
|
12
|
|
|
|
|
81
|
my $timezone = $self->_get_timezone($self->{main_obj}->get_timeline_config($timeline_name, "time_zone")); |
207
|
12
|
|
|
|
|
3122
|
my $dt = BusyBird::DateTime::Format->parse_datetime($timestamp_string); |
208
|
12
|
50
|
|
|
|
14483
|
return "" if !defined($dt); |
209
|
12
|
|
|
|
|
59
|
$dt->set_time_zone($timezone); |
210
|
12
|
|
|
|
|
234
|
$dt->set_locale($self->{main_obj}->get_timeline_config($timeline_name, "time_locale")); |
211
|
12
|
|
|
|
|
1549
|
return $dt->strftime($self->{main_obj}->get_timeline_config($timeline_name, "time_format")); |
212
|
|
|
|
|
|
|
}, |
213
|
|
|
|
|
|
|
bb_status_permalink => sub { |
214
|
31
|
|
|
31
|
|
2150
|
my ($status) = @_; |
215
|
31
|
|
|
|
|
129
|
my $builder = $self->{main_obj}->get_timeline_config($timeline_name, "status_permalink_builder"); |
216
|
|
|
|
|
|
|
my $url = try { |
217
|
31
|
|
|
|
|
1014
|
$builder->($status); |
218
|
|
|
|
|
|
|
}catch { |
219
|
0
|
|
|
|
|
0
|
my ($e) = @_; |
220
|
0
|
|
|
|
|
0
|
bblog("error", "Error in status_permalink_builder: $e"); |
221
|
0
|
|
|
|
|
0
|
undef; |
222
|
31
|
|
|
|
|
260
|
}; |
223
|
31
|
100
|
|
|
|
483
|
return (_is_valid_link_url($url) ? $url : ""); |
224
|
|
|
|
|
|
|
}, |
225
|
|
|
|
|
|
|
bb_text => html_builder { |
226
|
31
|
|
|
31
|
|
2461
|
my $status = shift; |
227
|
31
|
100
|
|
|
|
149
|
return "" if not defined $status->{text}; |
228
|
7
|
|
|
|
|
31
|
my $segments_ref = split_with_entities($status->{text}, $status->{entities}); |
229
|
7
|
|
|
|
|
11
|
my $result_text = ""; |
230
|
7
|
|
|
|
|
11
|
foreach my $segment (@$segments_ref) { |
231
|
|
|
|
|
|
|
my $rendered_text = try { |
232
|
24
|
|
|
|
|
714
|
$self->_render_text_segment($timeline_name, $segment, $status) |
233
|
|
|
|
|
|
|
}catch { |
234
|
0
|
|
|
|
|
0
|
my ($e) = @_; |
235
|
0
|
|
|
|
|
0
|
bblog("error", "Error while rendering text: $e"); |
236
|
0
|
|
|
|
|
0
|
undef; |
237
|
24
|
|
|
|
|
158
|
}; |
238
|
24
|
100
|
|
|
|
461
|
$result_text .= defined($rendered_text) ? $rendered_text : _escape_and_linkify_status_text($segment->{text}); |
239
|
|
|
|
|
|
|
} |
240
|
7
|
|
|
|
|
36
|
return $result_text; |
241
|
|
|
|
|
|
|
}, |
242
|
|
|
|
|
|
|
bb_attached_image_urls => sub { |
243
|
31
|
|
|
31
|
|
2492
|
my ($status) = @_; |
244
|
31
|
|
|
|
|
128
|
my $urls_builder = $self->{main_obj}->get_timeline_config($timeline_name, "attached_image_urls_builder"); |
245
|
|
|
|
|
|
|
my @image_urls = try { |
246
|
31
|
|
|
|
|
972
|
$urls_builder->($status); |
247
|
|
|
|
|
|
|
}catch { |
248
|
0
|
|
|
|
|
0
|
my ($e) = @_; |
249
|
0
|
|
|
|
|
0
|
bblog("error", "Error in attached_image_urls_builder: $e"); |
250
|
0
|
|
|
|
|
0
|
(); |
251
|
31
|
|
|
|
|
254
|
}; |
252
|
31
|
|
|
|
|
2003
|
return [grep { _is_valid_link_url($_) } @image_urls ]; |
|
11
|
|
|
|
|
18
|
|
253
|
|
|
|
|
|
|
}, |
254
|
27
|
|
|
|
|
394
|
}; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
{ |
258
|
|
|
|
|
|
|
my $timezone_cache = Cache::Memory::Simple->new(); |
259
|
|
|
|
|
|
|
my $CACHE_EXPIRATION_TIME = 3600 * 24; |
260
|
|
|
|
|
|
|
my $CACHE_SIZE_LIMIT = 100; |
261
|
|
|
|
|
|
|
sub _get_timezone { |
262
|
12
|
|
|
12
|
|
21
|
my ($self, $timezone_string) = @_; |
263
|
12
|
50
|
|
|
|
60
|
if($timezone_cache->count > $CACHE_SIZE_LIMIT) { |
264
|
0
|
|
|
|
|
0
|
$timezone_cache->purge(); |
265
|
0
|
0
|
|
|
|
0
|
if($timezone_cache->count > $CACHE_SIZE_LIMIT) { |
266
|
0
|
|
|
|
|
0
|
$timezone_cache->delete_all(); |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
return $timezone_cache->get_or_set($timezone_string, sub { |
270
|
3
|
|
|
3
|
|
68
|
return DateTime::TimeZone->new(name => $timezone_string), |
271
|
12
|
|
|
|
|
178
|
}, $CACHE_EXPIRATION_TIME); |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
sub _escape_and_linkify_status_text { |
276
|
14
|
|
|
14
|
|
20
|
my ($text) = @_; |
277
|
14
|
|
|
|
|
15
|
my $result_text = ""; |
278
|
14
|
|
|
|
|
14
|
my $remaining_index = 0; |
279
|
14
|
|
|
|
|
174
|
while($text =~ m/\G(.*?)($REGEXP_HTTP_URL)/sg) { |
280
|
3
|
|
|
|
|
7
|
my ($other_text, $url) = ($1, $2); |
281
|
3
|
|
|
|
|
14
|
$result_text .= html_escape($other_text); |
282
|
3
|
|
|
|
|
8
|
$result_text .= _html_link_status_text($url, $url); |
283
|
3
|
|
|
|
|
58
|
$remaining_index = pos($text); |
284
|
|
|
|
|
|
|
} |
285
|
14
|
|
|
|
|
146
|
$result_text .= html_escape(substr($text, $remaining_index)); |
286
|
14
|
|
|
|
|
49
|
return $result_text; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub _format_status_html_destructive { |
290
|
25
|
|
|
25
|
|
49
|
my ($self, $status, $timeline_name) = @_; |
291
|
25
|
50
|
|
|
|
76
|
$timeline_name = "" if not defined $timeline_name; |
292
|
25
|
100
|
66
|
|
|
110
|
if(ref($status->{retweeted_status}) eq "HASH" && (!defined($status->{busybird}) || ref($status->{busybird}) eq 'HASH')) { |
|
|
|
66
|
|
|
|
|
293
|
1
|
|
|
|
|
2
|
my $retweet = $status->{retweeted_status}; |
294
|
1
|
|
|
|
|
3
|
$status->{busybird}{retweeted_by_user} = $status->{user}; |
295
|
1
|
|
|
|
|
2
|
foreach my $key (qw(text created_at user entities)) { |
296
|
4
|
|
|
|
|
9
|
$status->{$key} = $retweet->{$key}; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
} |
299
|
25
|
|
|
|
|
88
|
return $self->{renderer}->render( |
300
|
|
|
|
|
|
|
"status.tx", |
301
|
|
|
|
|
|
|
{ss => safed($status), |
302
|
25
|
|
|
|
|
135
|
%{$self->template_functions_for_timeline($timeline_name)}} |
303
|
|
|
|
|
|
|
); |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
my %RESPONSE_FORMATTER_FOR_TL_GET_STATUSES = ( |
307
|
|
|
|
|
|
|
html => sub { |
308
|
|
|
|
|
|
|
my ($self, $timeline_name, $code, $response_object) = @_; |
309
|
|
|
|
|
|
|
if($code == 200) { |
310
|
|
|
|
|
|
|
my $result = ""; |
311
|
|
|
|
|
|
|
foreach my $status (@{$response_object->{statuses}}) { |
312
|
|
|
|
|
|
|
$result .= $self->_format_status_html_destructive($status, $timeline_name); |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
$result = Encode::encode('utf8', $result); |
315
|
|
|
|
|
|
|
return [200, ['Content-Type', 'text/html; charset=utf8'], [$result]]; |
316
|
|
|
|
|
|
|
}else { |
317
|
|
|
|
|
|
|
return $self->response_error_html($code, $response_object->{error}); |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
}, |
320
|
|
|
|
|
|
|
json => sub { |
321
|
|
|
|
|
|
|
my ($self, $timeline_name, $code, $response_object) = @_; |
322
|
|
|
|
|
|
|
return $self->response_json($code, $response_object); |
323
|
|
|
|
|
|
|
}, |
324
|
|
|
|
|
|
|
json_only_statuses => sub { |
325
|
|
|
|
|
|
|
my ($self, $timeline_name, $code, $response_object) = @_; |
326
|
|
|
|
|
|
|
my $res_array = defined($response_object->{error}) ? [] : $response_object->{statuses}; |
327
|
|
|
|
|
|
|
my $res_msg = try { |
328
|
|
|
|
|
|
|
to_json($res_array, {ascii => 1}); |
329
|
|
|
|
|
|
|
}catch { |
330
|
|
|
|
|
|
|
"[]" |
331
|
|
|
|
|
|
|
}; |
332
|
|
|
|
|
|
|
return [ |
333
|
|
|
|
|
|
|
$code, ['Content-Type' => 'application/json; charset=utf-8'], |
334
|
|
|
|
|
|
|
[$res_msg] |
335
|
|
|
|
|
|
|
]; |
336
|
|
|
|
|
|
|
}, |
337
|
|
|
|
|
|
|
); |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub response_statuses { |
340
|
87
|
|
|
87
|
1
|
24959
|
my ($self, %args) = @_; |
341
|
87
|
50
|
66
|
|
|
377
|
if(!defined($args{statuses}) && !defined($args{error})) { |
342
|
0
|
|
|
|
|
0
|
croak "stautses or error parameter is mandatory"; |
343
|
|
|
|
|
|
|
} |
344
|
87
|
|
|
|
|
222
|
foreach my $param_key (qw(http_code format)) { |
345
|
174
|
50
|
|
|
|
525
|
croak "$param_key parameter is mandatory" if not defined($args{$param_key}); |
346
|
|
|
|
|
|
|
} |
347
|
87
|
|
|
|
|
312
|
my $formatter = $RESPONSE_FORMATTER_FOR_TL_GET_STATUSES{lc($args{format})}; |
348
|
87
|
100
|
|
|
|
229
|
if(!defined($formatter)) { |
349
|
1
|
|
|
|
|
2
|
$formatter = $RESPONSE_FORMATTER_FOR_TL_GET_STATUSES{html}; |
350
|
1
|
|
|
|
|
2
|
delete $args{statuses}; |
351
|
1
|
|
|
|
|
4
|
$args{error} = "Unknown format: $args{format}"; |
352
|
1
|
|
|
|
|
2
|
$args{http_code} = 400; |
353
|
|
|
|
|
|
|
} |
354
|
87
|
100
|
|
|
|
758
|
return $formatter->($self, $args{timeline_name}, $args{http_code}, |
355
|
|
|
|
|
|
|
defined($args{error}) ? {error => $args{error}} |
356
|
|
|
|
|
|
|
: {error => undef, statuses => $args{statuses}}); |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
my %TIMELINE_CONFIG_FILTER_FOR = ( |
360
|
|
|
|
|
|
|
timeline_web_notifications => sub { defined($_[0]) ? "$_[0]" : ""}, |
361
|
|
|
|
|
|
|
acked_statuses_load_count => sub { $_[0] =~ /^\d+$/ ? $_[0] : undef }, |
362
|
|
|
|
|
|
|
default_level_threshold => sub { $_[0] =~ /^\d+$/ ? $_[0] : undef}, |
363
|
|
|
|
|
|
|
); |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
sub _create_timeline_config_json { |
366
|
13
|
|
|
13
|
|
24
|
my ($self, $timeline_name) = @_; |
367
|
13
|
|
|
|
|
27
|
my %config = (); |
368
|
13
|
|
|
|
|
48
|
foreach my $key (keys %TIMELINE_CONFIG_FILTER_FOR) { |
369
|
39
|
|
|
|
|
57
|
my $config_filter = $TIMELINE_CONFIG_FILTER_FOR{$key}; |
370
|
39
|
|
|
|
|
104
|
my $orig_value = $self->{main_obj}->get_timeline_config($timeline_name, $key); |
371
|
39
|
|
|
|
|
97
|
$config{$key} = $config_filter->($orig_value); |
372
|
|
|
|
|
|
|
} |
373
|
13
|
|
|
|
|
75
|
return to_json(\%config); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
sub response_timeline { |
377
|
21
|
|
|
21
|
1
|
767
|
my ($self, $timeline_name) = @_; |
378
|
21
|
|
|
|
|
124
|
my $timeline = $self->{main_obj}->get_timeline($timeline_name); |
379
|
21
|
100
|
|
|
|
323
|
return $self->response_notfound("Cannot find $timeline_name") if not defined($timeline); |
380
|
|
|
|
|
|
|
|
381
|
13
|
|
|
|
|
58
|
return $self->_response_template( |
382
|
|
|
|
|
|
|
template => "timeline.tx", |
383
|
|
|
|
|
|
|
args => { |
384
|
|
|
|
|
|
|
timeline_name => $timeline_name, |
385
|
|
|
|
|
|
|
timeline_config_json => $self->_create_timeline_config_json($timeline_name), |
386
|
|
|
|
|
|
|
post_button_url => $self->{main_obj}->get_timeline_config($timeline_name, "post_button_url"), |
387
|
|
|
|
|
|
|
attached_image_max_height => $self->{main_obj}->get_timeline_config($timeline_name, "attached_image_max_height"), |
388
|
|
|
|
|
|
|
attached_image_show_default_bool => |
389
|
|
|
|
|
|
|
($self->{main_obj}->get_timeline_config($timeline_name, "attached_image_show_default") eq "visible") |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
); |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub response_timeline_list { |
395
|
14
|
|
|
14
|
1
|
314451
|
my ($self, %args) = @_; |
396
|
14
|
|
|
|
|
52
|
foreach my $key (qw(timeline_unacked_counts total_page_num cur_page)) { |
397
|
42
|
50
|
|
|
|
169
|
croak "$key parameter is mandatory" if not defined $args{$key}; |
398
|
|
|
|
|
|
|
} |
399
|
14
|
50
|
|
|
|
72
|
croak "timeline_unacked_counts must be an array-ref" if ref($args{timeline_unacked_counts}) ne "ARRAY"; |
400
|
|
|
|
|
|
|
|
401
|
14
|
|
|
|
|
111
|
my %input_args = (last_page => $args{total_page_num} - 1); |
402
|
14
|
|
|
|
|
30
|
foreach my $input_key (qw(cur_page)) { |
403
|
14
|
|
|
|
|
49
|
$input_args{$input_key} = $args{$input_key}; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
35
|
|
|
|
|
639
|
$input_args{timeline_unacked_counts_json} = [map { |
407
|
14
|
|
|
|
|
38
|
+{name => $_->{name}, counts_json => to_json($_->{counts})} |
408
|
14
|
|
|
|
|
28
|
} @{$args{timeline_unacked_counts}}]; |
409
|
|
|
|
|
|
|
|
410
|
14
|
|
|
|
|
390
|
my $pager_entry_max = $self->{main_obj}->get_config('timeline_list_pager_entry_max'); |
411
|
14
|
|
|
|
|
59
|
my $left_margin = int($pager_entry_max / 2); |
412
|
14
|
|
|
|
|
30
|
my $right_margin = $pager_entry_max - $left_margin; |
413
|
14
|
100
|
|
|
|
106
|
$input_args{page_list} = |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
414
|
|
|
|
|
|
|
$args{total_page_num} <= $pager_entry_max ? [0 .. ($args{total_page_num} - 1)] |
415
|
|
|
|
|
|
|
: $args{cur_page} <= $left_margin ? [0 .. ($pager_entry_max - 1)] |
416
|
|
|
|
|
|
|
: $args{cur_page} >= ($args{total_page_num} - $right_margin) |
417
|
|
|
|
|
|
|
? [($args{total_page_num} - $pager_entry_max) .. ($args{total_page_num} - 1)] |
418
|
|
|
|
|
|
|
: [($args{cur_page} - $left_margin) .. ($args{cur_page} + $right_margin - 1)]; |
419
|
|
|
|
|
|
|
$input_args{page_path} = sub { |
420
|
166
|
|
|
166
|
|
216
|
my ($page) = @_; |
421
|
166
|
|
|
|
|
326
|
return _make_path($self->{script_name}, "/?page=$page"); |
422
|
14
|
|
|
|
|
95
|
}; |
423
|
14
|
|
|
|
|
71
|
return $self->_response_template( |
424
|
|
|
|
|
|
|
template => "timeline_list.tx", |
425
|
|
|
|
|
|
|
args => \%input_args, |
426
|
|
|
|
|
|
|
); |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
1; |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
__END__ |