ayadn 3.0 → 4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -4
  3. data/CHANGELOG.md +12 -4
  4. data/README.md +2 -5
  5. data/ayadn.gemspec +0 -2
  6. data/doc/01-index.md +0 -3
  7. data/doc/02-install.md +0 -4
  8. data/doc/06-post.md +0 -16
  9. data/doc/{18-contact.md → 16-contact.md} +0 -0
  10. data/doc/{19-examples.md → 17-examples.md} +0 -0
  11. data/doc/18-develop.md +165 -0
  12. data/lib/ayadn/action.rb +206 -396
  13. data/lib/ayadn/alias.rb +1 -1
  14. data/lib/ayadn/annotations.rb +15 -27
  15. data/lib/ayadn/api.rb +39 -28
  16. data/lib/ayadn/app.rb +19 -29
  17. data/lib/ayadn/authorize.rb +22 -13
  18. data/lib/ayadn/blacklist.rb +6 -19
  19. data/lib/ayadn/channel_object.rb +75 -0
  20. data/lib/ayadn/check.rb +19 -11
  21. data/lib/ayadn/cnx.rb +9 -15
  22. data/lib/ayadn/databases.rb +15 -27
  23. data/lib/ayadn/debug.rb +9 -9
  24. data/lib/ayadn/descriptions.rb +1 -99
  25. data/lib/ayadn/diagnostics.rb +16 -15
  26. data/lib/ayadn/endpoints.rb +18 -22
  27. data/lib/ayadn/errors.rb +1 -1
  28. data/lib/ayadn/fileops.rb +12 -12
  29. data/lib/ayadn/filtered_post_object.rb +11 -0
  30. data/lib/ayadn/ids.rb +0 -3
  31. data/lib/ayadn/logs.rb +4 -4
  32. data/lib/ayadn/mark.rb +34 -30
  33. data/lib/ayadn/nicerank.rb +7 -7
  34. data/lib/ayadn/nowplaying.rb +8 -22
  35. data/lib/ayadn/pinboard.rb +8 -12
  36. data/lib/ayadn/post.rb +18 -18
  37. data/lib/ayadn/post_object.rb +118 -0
  38. data/lib/ayadn/preferences_object.rb +290 -0
  39. data/lib/ayadn/profile.rb +2 -2
  40. data/lib/ayadn/scroll.rb +58 -67
  41. data/lib/ayadn/search.rb +22 -15
  42. data/lib/ayadn/set.rb +93 -83
  43. data/lib/ayadn/settings.rb +25 -33
  44. data/lib/ayadn/status.rb +24 -26
  45. data/lib/ayadn/stream.rb +68 -66
  46. data/lib/ayadn/stream_object.rb +56 -0
  47. data/lib/ayadn/switch.rb +2 -2
  48. data/lib/ayadn/user_object.rb +116 -0
  49. data/lib/ayadn/version.rb +1 -1
  50. data/lib/ayadn/view.rb +255 -278
  51. data/lib/ayadn/workers.rb +172 -174
  52. data/spec/integration/action_spec.rb +55 -34
  53. data/spec/mock/ayadn.sqlite +0 -0
  54. data/spec/unit/annotations_spec.rb +54 -41
  55. data/spec/unit/api_spec.rb +78 -7
  56. data/spec/unit/blacklistworkers_spec.rb +92 -20
  57. data/spec/unit/databases_spec.rb +117 -36
  58. data/spec/unit/endpoints_spec.rb +82 -10
  59. data/spec/unit/nicerank_spec.rb +56 -27
  60. data/spec/unit/post_spec.rb +94 -21
  61. data/spec/unit/set_spec.rb +141 -210
  62. data/spec/unit/view_spec.rb +105 -32
  63. data/spec/unit/workers_spec.rb +143 -52
  64. metadata +12 -37
  65. data/doc/16-movie.md +0 -39
  66. data/doc/17-tvshow.md +0 -46
  67. data/lib/ayadn/nowwatching.rb +0 -118
  68. data/lib/ayadn/tvshow.rb +0 -162
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+ module Ayadn
3
+ class FilteredPost
4
+
5
+ attr_accessor :input, :is_human, :name, :count, :id, :thread_id, :username, :user_id, :nicerank, :handle, :type, :date, :date_short, :you_starred, :source_name, :source_link, :canonical_url, :tags, :links, :mentions, :directed_to, :checkins, :has_checkins, :is_repost, :repost_of, :original_poster, :raw_text, :text, :is_starred, :num_stars, :num_replies, :is_reply, :reply_to, :num_reposts
6
+
7
+ def initialize post
8
+ @input = post
9
+ end
10
+ end
11
+ end
data/lib/ayadn/ids.rb CHANGED
@@ -3,9 +3,6 @@ module Ayadn
3
3
  class Settings
4
4
  CLIENT_ID = "hFsCGArAjgJkYBHTHbZnUvzTmL4vaLHL"
5
5
  end
6
- class TvShow
7
- TVDB_API_KEY = 'E874ACBC542CAA53'
8
- end
9
6
  class Endpoints
10
7
  CALLBACK_URL = "http://aya.io/ayadn/auth.html"
11
8
  end
data/lib/ayadn/logs.rb CHANGED
@@ -7,13 +7,13 @@ module Ayadn
7
7
  end
8
8
 
9
9
  def self.create_logger
10
- @rec = Logger.new(Settings.config[:paths][:log] + "/ayadn.log", 'monthly')
10
+ @rec = Logger.new(Settings.config.paths.log + "/ayadn.log", 'monthly')
11
11
  @rec.formatter = proc do |severity, datetime, progname, msg|
12
- "#{datetime} (#{Settings.config[:version]}) #{severity} * #{msg}\n"
12
+ "#{datetime} (#{Settings.config.version}) #{severity} * #{msg}\n"
13
13
  end
14
- @nr = Logger.new(Settings.config[:paths][:log] + "/nicerank.log", 'monthly')
14
+ @nr = Logger.new(Settings.config.paths.log + "/nicerank.log", 'monthly')
15
15
  @nr.formatter = proc do |severity, datetime, progname, msg|
16
- "#{datetime} (#{Settings.config[:version]}) #{severity} * #{msg}\n"
16
+ "#{datetime} (#{Settings.config.version}) #{severity} * #{msg}\n"
17
17
  end
18
18
  end
19
19
 
data/lib/ayadn/mark.rb CHANGED
@@ -10,6 +10,7 @@ module Ayadn
10
10
  begin
11
11
  init
12
12
  status = Status.new
13
+ workers = Workers.new(status)
13
14
  unless args.empty?
14
15
  double = args.dup
15
16
  post_id, convo_title = double.shift, double.join(' ')
@@ -17,41 +18,42 @@ module Ayadn
17
18
  status.wrong_arguments
18
19
  exit
19
20
  end
20
- Check.new.bad_post_id(post_id)
21
+ Check.new(status).bad_post_id(post_id)
21
22
  if options[:force]
22
- Settings.global[:force] = true
23
+ Settings.global.force = true
23
24
  else
24
- post_id = Workers.new.get_real_post_id(post_id)
25
+ post_id = workers.get_real_post_id(post_id)
25
26
  end
26
27
  convo_title = post_id if convo_title == ''
27
- api, workers, view = API.new, Workers.new, View.new
28
+ api, workers, view = API.new, workers, View.new(status, workers)
28
29
  users, bucket = [], []
29
30
  view.clear_screen
30
31
  status.info(:connected, "analyzing conversation", :yellow)
31
32
  resp = api.get_convo(post_id, options)
32
- posts = workers.build_posts(resp['data'].reverse)
33
- posts.each do |id, post|
34
- users << "#{post[:original_poster]}"
35
- post[:mentions].each {|mention| users << "#{mention}"}
33
+ stream_object = StreamObject.new(resp)
34
+ posts = workers.build_posts(stream_object.posts.reverse)
35
+ posts.each do |post|
36
+ users << "#{post.original_poster}"
37
+ post.mentions.each {|mention| users << "#{mention}"}
36
38
  bucket << post
37
39
  end
38
40
  users.uniq!
39
41
  now = Time.now.to_s
40
42
  bookmark = {
41
43
  'id' => post_id,
42
- 'root_id' => bucket[0][:id],
43
- 'last_id' => (bucket.last)[:id],
44
+ 'root_id' => bucket[0].id,
45
+ 'last_id' => bucket.last.id,
44
46
  'title' => convo_title,
45
- 'first_date' => bucket[0][:date],
46
- 'last_date' => (bucket.last)[:date],
47
+ 'first_date' => bucket[0].date,
48
+ 'last_date' => bucket.last.date,
47
49
  'mark_date' => now[0..18],
48
- 'first_poster' => bucket[0][:original_poster],
49
- 'last_poster' => (bucket.last)[:username],
50
+ 'first_poster' => bucket[0].original_poster,
51
+ 'last_poster' => bucket.last.username,
50
52
  'users' => users,
51
53
  'size' => bucket.length,
52
- 'url' => bucket[0][:canonical_url],
53
- 'root_text' => bucket[0][:raw_text],
54
- 'root_colorized_text' => bucket[0][:text]
54
+ 'url' => bucket[0].canonical_url,
55
+ 'root_text' => bucket[0].raw_text,
56
+ 'root_colorized_text' => bucket[0].text
55
57
  }
56
58
  view.clear_screen
57
59
  status.info(:done, "bookmarked conversation:", :green)
@@ -114,17 +116,18 @@ module Ayadn
114
116
  begin
115
117
  init
116
118
  status = Status.new
119
+ workers = Workers.new(status)
117
120
  if args.empty?
118
121
  status.wrong_arguments
119
122
  exit
120
123
  else
121
124
  post_id = args[0]
122
125
  end
123
- Check.new.bad_post_id(post_id)
126
+ Check.new(status).bad_post_id(post_id)
124
127
  if options[:force]
125
- Settings.global[:force] = true
128
+ Settings.global.force = true
126
129
  else
127
- post_id = Workers.new.get_real_post_id(post_id)
130
+ post_id = workers.get_real_post_id(post_id)
128
131
  end
129
132
  Databases.delete_bookmark post_id
130
133
  status.done
@@ -140,17 +143,18 @@ module Ayadn
140
143
  begin
141
144
  init
142
145
  status = Status.new
146
+ workers = Workers.new(status)
143
147
  unless args.empty? || args[1].nil?
144
148
  arguments = args.dup
145
149
  post_id = arguments.shift
146
150
  else
147
151
  abort Status.wrong_arguments
148
152
  end
149
- Check.new.bad_post_id(post_id)
153
+ Check.new(status).bad_post_id(post_id)
150
154
  if options[:force]
151
- Settings.global[:force] = true
155
+ Settings.global.force = true
152
156
  else
153
- post_id = Workers.new.get_real_post_id(post_id)
157
+ post_id = workers.get_real_post_id(post_id)
154
158
  end
155
159
  Databases.rename_bookmark post_id, arguments.join(" ")
156
160
  status.done
@@ -164,25 +168,25 @@ module Ayadn
164
168
  def make_entry content
165
169
  entry = ""
166
170
  entry << "Post id:".color(:cyan)
167
- entry << "\t#{content['id']}\n".color(Settings.options[:colors][:username])
171
+ entry << "\t#{content['id']}\n".color(Settings.options.colors.username)
168
172
  unless content['title'].is_integer?
169
173
  entry << "Title:".color(:cyan)
170
- entry << "\t\t#{content['title']}\n".color(Settings.options[:colors][:id])
174
+ entry << "\t\t#{content['title']}\n".color(Settings.options.colors.id)
171
175
  end
172
176
  entry << "Date:".color(:cyan)
173
- entry << "\t\t#{content['first_date']}\n".color(Settings.options[:colors][:date])
177
+ entry << "\t\t#{content['first_date']}\n".color(Settings.options.colors.date)
174
178
  # entry << "Bookmarked:".color(:cyan)
175
- # entry << "\t#{content['mark_date']}\n".color(Settings.options[:colors][:date])
179
+ # entry << "\t#{content['mark_date']}\n".color(Settings.options.colors.date)
176
180
  entry << "Posts:".color(:cyan)
177
- entry << "\t\t#{content['size']}\n".color(Settings.options[:colors][:name])
181
+ entry << "\t\t#{content['size']}\n".color(Settings.options.colors.name)
178
182
  entry << "Posters:".color(:cyan)
179
183
  posters = []
180
184
  content['users'].each {|mention| posters << "@#{mention}"}
181
- entry << "\t#{posters.join(', ')}\n".color(Settings.options[:colors][:mentions])
185
+ entry << "\t#{posters.join(', ')}\n".color(Settings.options.colors.mentions)
182
186
  # entry << "First:\t\t@#{content['first_poster']}\n"
183
187
  # entry << "Last:\t\t@#{content['last_poster']}\n"
184
188
  entry << "Link:".color(:cyan)
185
- entry << "\t\t#{content['url']}\n".color(Settings.options[:colors][:link])
189
+ entry << "\t\t#{content['url']}\n".color(Settings.options.colors.link)
186
190
  entry << "Beginning:".color(:cyan)
187
191
  text = content['root_text'].gsub(/[\r\n]/, ' ')
188
192
  if text.length <= 60
@@ -23,8 +23,8 @@ module Ayadn
23
23
  def get_ranks stream
24
24
  begin
25
25
  user_ids, niceranks = [], {}
26
- stream['data'].each do |post|
27
- id = post['user']['id']
26
+ stream.posts.each do |post|
27
+ id = post.user.id
28
28
  user_ids << id if @store[id].nil?
29
29
  end
30
30
  user_ids.uniq!
@@ -47,7 +47,7 @@ module Ayadn
47
47
  parsed['data'].each do |obj|
48
48
  res = @store[obj['user_id']]
49
49
  if res.nil?
50
- obj['is_human'] == true ? is_human = 1 : is_human = 0
50
+ obj['is_human'] ? is_human = 1 : is_human = 0
51
51
  content = {
52
52
  rank: obj['rank'],
53
53
  is_human: is_human
@@ -61,14 +61,14 @@ module Ayadn
61
61
  end
62
62
 
63
63
 
64
- @posts += stream['data'].size
64
+ @posts += stream.posts.size
65
65
  @ids += user_ids.size
66
66
 
67
- if Settings.options[:timeline][:debug] == true
67
+ if Settings.options.timeline.debug
68
68
  deb = "\n"
69
69
  deb << "+ NICERANK\n"
70
70
  deb << "* t#{Time.now.to_i}\n"
71
- deb << "Posts:\t\t#{stream['data'].size}\n"
71
+ deb << "Posts:\t\t#{stream.posts.size}\n"
72
72
  deb << "Requested NR:\t#{user_ids.size}\n"
73
73
  deb << "* TOTALS\n"
74
74
  deb << "Posts:\t\t#{@posts}\n"
@@ -76,7 +76,7 @@ module Ayadn
76
76
  deb << "DB hits:\t#{@hits}\n"
77
77
  deb << "Uniques:\t#{@store.count}\n"
78
78
  deb << "\n"
79
- puts deb.color(Settings.options[:colors][:debug])
79
+ puts deb.color(Settings.options.colors.debug)
80
80
  Logs.rec.debug "NICERANK/POSTS: #{@posts}"
81
81
  Logs.rec.debug "NICERANK/NR CALLS: #{@ids}"
82
82
  Logs.rec.debug "NICERANK/CACHE HITS: #{@hits}"
@@ -3,13 +3,7 @@ module Ayadn
3
3
 
4
4
  class NowPlaying
5
5
 
6
- # Warning
7
- # comment next line
8
6
  require_relative "ids"
9
- # uncomment next line and insert your own codes
10
- # AFFILIATE_SUFFIX = ""
11
- # DEEZER_APP_ID = ""
12
- # DEEZER_AUTH_URL = ""
13
7
 
14
8
  begin
15
9
  require 'rss'
@@ -19,11 +13,12 @@ module Ayadn
19
13
  exit
20
14
  end
21
15
 
22
- def initialize api, view, workers, options = {}
16
+ def initialize api, view, workers, status, options = {}
23
17
  @api = api
24
18
  @view = view
25
19
  @workers = workers
26
- @status = Status.new
20
+ @status = status
21
+ # @status = Status.new
27
22
  unless options[:hashtag]
28
23
  @hashtag = "#nowplaying"
29
24
  else
@@ -88,7 +83,7 @@ module Ayadn
88
83
 
89
84
  def itunes options
90
85
  begin
91
- unless Settings.config[:platform] =~ /darwin/
86
+ unless Settings.config.platform =~ /darwin/
92
87
  @status.error_only_osx
93
88
  exit
94
89
  end
@@ -141,8 +136,8 @@ module Ayadn
141
136
  unless options[:no_url] || store.nil?
142
137
  text_to_post += "\n \n[iTunes Store](#{store['link']})"
143
138
  end
144
- poster = Post.new
145
- poster.post_size_error(text_to_post) if poster.post_size_ok?(text_to_post) == false
139
+ poster = Post.new(@status)
140
+ poster.post_size_error(text_to_post) if !poster.post_size_ok?(text_to_post)
146
141
  @view.clear_screen
147
142
  @status.writing
148
143
  show_nowplaying("\n#{before}", options, store)
@@ -189,7 +184,7 @@ module Ayadn
189
184
  visible: visible
190
185
  }
191
186
  resp = poster.post(dic)
192
- FileOps.save_post(resp) if Settings.options[:backup][:posts]
187
+ FileOps.save_post(resp) if Settings.options.backup.posts
193
188
  @view.show_posted(resp)
194
189
  rescue => e
195
190
  @status.wtf
@@ -245,10 +240,6 @@ module Ayadn
245
240
 
246
241
  def get_itunes_store url, artist, track
247
242
  results = JSON.load(CNX.download(URI.escape(url)))['results']
248
-
249
- #
250
- # require 'pp'; pp results; exit
251
- #
252
243
 
253
244
  unless results.nil?
254
245
 
@@ -266,10 +257,6 @@ module Ayadn
266
257
  candidates << e if e['artistName'].downcase == artist.downcase
267
258
  end
268
259
 
269
- #
270
- # require "pp";pp candidates; exit
271
- #
272
-
273
260
  candidate = if candidates.empty?
274
261
  results[0]
275
262
  else
@@ -309,10 +296,9 @@ module Ayadn
309
296
  end
310
297
 
311
298
  def show_nowplaying(text, options, store)
312
- # @status.to_be_posted
313
299
  thor = Thor::Shell::Basic.new
314
300
  text.split("\n").each do |line|
315
- thor.say_status(nil, line.color(Settings.options[:colors][:excerpt]))
301
+ thor.say_status(nil, line.color(Settings.options.colors.excerpt))
316
302
  end
317
303
  puts "\n"
318
304
  unless options['no_url'] || store['code'] != 200
@@ -2,32 +2,28 @@
2
2
  module Ayadn
3
3
  class PinBoard
4
4
 
5
- def initialize
6
- @status = Status.new
7
- end
8
-
9
5
  def has_credentials_file?
10
- File.exist?(Ayadn::Settings.config[:paths][:auth] + '/pinboard.data')
6
+ File.exist?(Ayadn::Settings.config.paths.auth + '/pinboard.data')
11
7
  end
12
8
 
13
- def ask_credentials
9
+ def ask_credentials status = Status.new
14
10
  begin
15
- @status.pin_username
11
+ status.pin_username
16
12
  pin_username = STDIN.gets.chomp()
17
- @status.pin_password
13
+ status.pin_password
18
14
  pin_password = STDIN.noecho(&:gets).chomp()
19
15
  rescue Interrupt
20
- @status.canceled
16
+ status.canceled
21
17
  exit
22
18
  rescue => e
23
- @status.wtf
19
+ status.wtf
24
20
  Errors.global_error({error: e, caller: caller, data: [pin_username]})
25
21
  end
26
22
  save_credentials(encode(pin_username, pin_password))
27
23
  end
28
24
 
29
25
  def load_credentials
30
- decode(File.read(Ayadn::Settings.config[:paths][:auth] + '/pinboard.data'))
26
+ decode(File.read(Ayadn::Settings.config.paths.auth + '/pinboard.data'))
31
27
  end
32
28
 
33
29
  def pin(data)
@@ -42,7 +38,7 @@ module Ayadn
42
38
  end
43
39
 
44
40
  def save_credentials(encoded_pinboard_credentials)
45
- File.write(Ayadn::Settings.config[:paths][:auth] + '/pinboard.data', encoded_pinboard_credentials)
41
+ File.write(Ayadn::Settings.config.paths.auth + '/pinboard.data', encoded_pinboard_credentials)
46
42
  end
47
43
 
48
44
  def encode(username, password)
data/lib/ayadn/post.rb CHANGED
@@ -4,8 +4,9 @@ module Ayadn
4
4
 
5
5
  require_relative "annotations"
6
6
 
7
- def initialize
8
- @status = Status.new
7
+ def initialize status = Status.new
8
+ @status = status
9
+ @markdown_link_regex = /\[([^\]]+)\]\(([^)]+)\)/
9
10
  end
10
11
 
11
12
  def post(dic)
@@ -13,16 +14,16 @@ module Ayadn
13
14
  end
14
15
 
15
16
  def reply(dic)
16
- replied_to = dic[:reply_to].values[0]
17
- reply = replied_to[:handle].dup
17
+ replied_to = dic[:reply_to]
18
+ reply = replied_to.handle.dup
18
19
  reply << " #{dic[:text]}"
19
- replied_to[:mentions].uniq!
20
- replied_to[:mentions].each do |m|
21
- next if m == replied_to[:username]
22
- next if m == Settings.config[:identity][:username]
20
+ replied_to.mentions.uniq!
21
+ replied_to.mentions.each do |m|
22
+ next if m == replied_to.username
23
+ next if m == Settings.config.identity.username
23
24
  reply << " @#{m}"
24
25
  end
25
- post_size_error(reply) if post_size_ok?(reply) == false
26
+ post_size_error(reply) if !post_size_ok?(reply)
26
27
  dic[:text] = reply
27
28
  dic[:reply_to] = dic[:id]
28
29
  send_content(Endpoints.new.posts_url, payload_reply(dic))
@@ -87,10 +88,9 @@ module Ayadn
87
88
  def auto_readline
88
89
  loop do
89
90
  begin
90
- #while buffer = Readline.readline("#{Settings.config[:identity][:handle]} >> ".color(:red))
91
91
  while buffer = Readline.readline(">> ".color(:red))
92
92
  resp = post({text: buffer})
93
- FileOps.save_post(resp) if Settings.options[:backup][:posts]
93
+ FileOps.save_post(resp) if Settings.options.backup.posts
94
94
  @status.done
95
95
  end
96
96
  rescue Interrupt
@@ -116,25 +116,25 @@ module Ayadn
116
116
 
117
117
  def post_size_ok?(post) # works on a string, returns boolean
118
118
  text = keep_text_from_markdown_links(post)
119
- size, max_size = text.length, Settings.config[:post_max_length]
119
+ size, max_size = text.length, Settings.config.post_max_length
120
120
  (size >= 1 && size <= max_size)
121
121
  end
122
122
 
123
123
  def message_size_ok?(message) # works on a string, returns boolean
124
124
  text = keep_text_from_markdown_links(message)
125
- size, max_size = text.length, Settings.config[:message_max_length]
125
+ size, max_size = text.length, Settings.config.message_max_length
126
126
  (size >= 1 && size <= max_size)
127
127
  end
128
128
 
129
129
  def post_size_error(post)
130
130
  text = keep_text_from_markdown_links(post)
131
- size, max_size = text.length, Settings.config[:post_max_length]
131
+ size, max_size = text.length, Settings.config.post_max_length
132
132
  bad_text_size(post, size, max_size)
133
133
  end
134
134
 
135
135
  def message_size_error(message)
136
136
  text = keep_text_from_markdown_links(message)
137
- size, max_size = text.length, Settings.config[:message_max_length]
137
+ size, max_size = text.length, Settings.config.message_max_length
138
138
  bad_text_size(message, size, max_size)
139
139
  end
140
140
 
@@ -151,12 +151,12 @@ module Ayadn
151
151
  end
152
152
 
153
153
  def keep_text_from_markdown_links(str)
154
- str.gsub(/\[([^\]]+)\]\(([^)]+)\)/, '\1')
154
+ str.gsub(@markdown_link_regex, '\1')
155
155
  end
156
156
 
157
157
  def markdown_extract(str)
158
- result = str.gsub(/\[([^\]]+)\]\(([^)]+)\)/, '\1|||\2')
159
- result.split('|||') #=> [text, link]
158
+ result = str.gsub(@markdown_link_regex, '\1|||\2')
159
+ result.split('|||') #=> [text, link]
160
160
  end
161
161
 
162
162
  def error_text_empty