atig 0.0.1

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 (171) hide show
  1. data/.gitignore +24 -0
  2. data/Gemfile +3 -0
  3. data/README.mkdn +52 -0
  4. data/Rakefile +15 -0
  5. data/atig.gemspec +25 -0
  6. data/bin/atig +74 -0
  7. data/docs/OMakefile +32 -0
  8. data/docs/OMakeroot +45 -0
  9. data/docs/_static/allow.png +0 -0
  10. data/docs/_static/emacs.png +0 -0
  11. data/docs/_static/irc_setting.png +0 -0
  12. data/docs/_static/irssi.png +0 -0
  13. data/docs/_static/limechat.png +0 -0
  14. data/docs/_static/limechat_s.png +0 -0
  15. data/docs/_static/oauth_channel.png +0 -0
  16. data/docs/_static/screenshot.png +0 -0
  17. data/docs/_static/structure.png +0 -0
  18. data/docs/_static/verify.png +0 -0
  19. data/docs/changelog.rst +96 -0
  20. data/docs/commandline_options.rst +21 -0
  21. data/docs/commands.rst +84 -0
  22. data/docs/conf.py +194 -0
  23. data/docs/config.rst +159 -0
  24. data/docs/feature.rst +41 -0
  25. data/docs/graphics.graffle +1995 -0
  26. data/docs/hacking_guide.rst +43 -0
  27. data/docs/index.rst +109 -0
  28. data/docs/irc.rst +31 -0
  29. data/docs/options.rst +75 -0
  30. data/docs/quickstart.rst +89 -0
  31. data/docs/resize.sh +7 -0
  32. data/docs/tiarra.rst +2 -0
  33. data/docs/tig.rst +21 -0
  34. data/lib/atig.rb +19 -0
  35. data/lib/atig/agent.rb +8 -0
  36. data/lib/atig/agent/agent.rb +38 -0
  37. data/lib/atig/agent/clenup.rb +23 -0
  38. data/lib/atig/agent/dm.rb +35 -0
  39. data/lib/atig/agent/following.rb +45 -0
  40. data/lib/atig/agent/full_list.rb +20 -0
  41. data/lib/atig/agent/list.rb +55 -0
  42. data/lib/atig/agent/list_status.rb +46 -0
  43. data/lib/atig/agent/mention.rb +13 -0
  44. data/lib/atig/agent/other_list.rb +18 -0
  45. data/lib/atig/agent/own_list.rb +18 -0
  46. data/lib/atig/agent/stream_follow.rb +38 -0
  47. data/lib/atig/agent/timeline.rb +13 -0
  48. data/lib/atig/agent/user_stream.rb +31 -0
  49. data/lib/atig/basic_twitter.rb +116 -0
  50. data/lib/atig/bitly.rb +52 -0
  51. data/lib/atig/channel.rb +5 -0
  52. data/lib/atig/channel/channel.rb +17 -0
  53. data/lib/atig/channel/dm.rb +14 -0
  54. data/lib/atig/channel/list.rb +76 -0
  55. data/lib/atig/channel/mention.rb +20 -0
  56. data/lib/atig/channel/retweet.rb +28 -0
  57. data/lib/atig/channel/timeline.rb +74 -0
  58. data/lib/atig/command.rb +21 -0
  59. data/lib/atig/command/autofix.rb +58 -0
  60. data/lib/atig/command/command.rb +24 -0
  61. data/lib/atig/command/command_helper.rb +95 -0
  62. data/lib/atig/command/destroy.rb +44 -0
  63. data/lib/atig/command/dm.rb +31 -0
  64. data/lib/atig/command/favorite.rb +27 -0
  65. data/lib/atig/command/info.rb +50 -0
  66. data/lib/atig/command/limit.rb +15 -0
  67. data/lib/atig/command/location.rb +23 -0
  68. data/lib/atig/command/name.rb +18 -0
  69. data/lib/atig/command/option.rb +37 -0
  70. data/lib/atig/command/refresh.rb +18 -0
  71. data/lib/atig/command/reply.rb +37 -0
  72. data/lib/atig/command/retweet.rb +63 -0
  73. data/lib/atig/command/search.rb +51 -0
  74. data/lib/atig/command/spam.rb +26 -0
  75. data/lib/atig/command/status.rb +41 -0
  76. data/lib/atig/command/thread.rb +44 -0
  77. data/lib/atig/command/time.rb +32 -0
  78. data/lib/atig/command/uptime.rb +32 -0
  79. data/lib/atig/command/user.rb +42 -0
  80. data/lib/atig/command/user_info.rb +27 -0
  81. data/lib/atig/command/version.rb +49 -0
  82. data/lib/atig/command/whois.rb +39 -0
  83. data/lib/atig/db/db.rb +60 -0
  84. data/lib/atig/db/followings.rb +131 -0
  85. data/lib/atig/db/listenable.rb +22 -0
  86. data/lib/atig/db/lists.rb +76 -0
  87. data/lib/atig/db/roman.rb +30 -0
  88. data/lib/atig/db/sized_uniq_array.rb +62 -0
  89. data/lib/atig/db/sql.rb +35 -0
  90. data/lib/atig/db/statuses.rb +147 -0
  91. data/lib/atig/db/transaction.rb +47 -0
  92. data/lib/atig/exception_util.rb +26 -0
  93. data/lib/atig/gateway.rb +62 -0
  94. data/lib/atig/gateway/channel.rb +99 -0
  95. data/lib/atig/gateway/session.rb +326 -0
  96. data/lib/atig/http.rb +95 -0
  97. data/lib/atig/ifilter.rb +7 -0
  98. data/lib/atig/ifilter/expand_url.rb +74 -0
  99. data/lib/atig/ifilter/retweet.rb +14 -0
  100. data/lib/atig/ifilter/retweet_time.rb +16 -0
  101. data/lib/atig/ifilter/sanitize.rb +18 -0
  102. data/lib/atig/ifilter/strip.rb +15 -0
  103. data/lib/atig/ifilter/utf7.rb +26 -0
  104. data/lib/atig/ifilter/xid.rb +36 -0
  105. data/lib/atig/levenshtein.rb +49 -0
  106. data/lib/atig/monkey.rb +4 -0
  107. data/lib/atig/oauth-patch.rb +40 -0
  108. data/lib/atig/oauth.rb +55 -0
  109. data/lib/atig/ofilter.rb +4 -0
  110. data/lib/atig/ofilter/escape_url.rb +102 -0
  111. data/lib/atig/ofilter/footer.rb +20 -0
  112. data/lib/atig/ofilter/geo.rb +17 -0
  113. data/lib/atig/ofilter/short_url.rb +47 -0
  114. data/lib/atig/option.rb +90 -0
  115. data/lib/atig/scheduler.rb +79 -0
  116. data/lib/atig/search.rb +22 -0
  117. data/lib/atig/search_twitter.rb +21 -0
  118. data/lib/atig/sized_hash.rb +33 -0
  119. data/lib/atig/stream.rb +66 -0
  120. data/lib/atig/twitter.rb +79 -0
  121. data/lib/atig/twitter_struct.rb +63 -0
  122. data/lib/atig/unu.rb +27 -0
  123. data/lib/atig/update_checker.rb +53 -0
  124. data/lib/atig/url_escape.rb +62 -0
  125. data/lib/atig/util.rb +16 -0
  126. data/lib/atig/version.rb +3 -0
  127. data/lib/memory_profiler.rb +77 -0
  128. data/spec/command/autofix_spec.rb +35 -0
  129. data/spec/command/destroy_spec.rb +98 -0
  130. data/spec/command/dm_spec.rb +28 -0
  131. data/spec/command/favorite_spec.rb +55 -0
  132. data/spec/command/limit_spec.rb +27 -0
  133. data/spec/command/location_spec.rb +25 -0
  134. data/spec/command/name_spec.rb +19 -0
  135. data/spec/command/option_spec.rb +133 -0
  136. data/spec/command/refresh_spec.rb +22 -0
  137. data/spec/command/reply_spec.rb +79 -0
  138. data/spec/command/retweet_spec.rb +66 -0
  139. data/spec/command/spam_spec.rb +27 -0
  140. data/spec/command/status_spec.rb +44 -0
  141. data/spec/command/thread_spec.rb +91 -0
  142. data/spec/command/time_spec.rb +52 -0
  143. data/spec/command/uptime_spec.rb +55 -0
  144. data/spec/command/user_info_spec.rb +42 -0
  145. data/spec/command/user_spec.rb +50 -0
  146. data/spec/command/version_spec.rb +67 -0
  147. data/spec/command/whois_spec.rb +78 -0
  148. data/spec/db/followings_spec.rb +100 -0
  149. data/spec/db/listenable_spec.rb +32 -0
  150. data/spec/db/lists_spec.rb +104 -0
  151. data/spec/db/roman_spec.rb +17 -0
  152. data/spec/db/sized_uniq_array_spec.rb +63 -0
  153. data/spec/db/statuses_spec.rb +180 -0
  154. data/spec/ifilter/expand_url_spec.rb +44 -0
  155. data/spec/ifilter/retweet_spec.rb +28 -0
  156. data/spec/ifilter/retweet_time_spec.rb +25 -0
  157. data/spec/ifilter/sanitize_spec.rb +25 -0
  158. data/spec/ifilter/sid_spec.rb +29 -0
  159. data/spec/ifilter/strip_spec.rb +23 -0
  160. data/spec/ifilter/tid_spec.rb +29 -0
  161. data/spec/ifilter/utf7_spec.rb +30 -0
  162. data/spec/levenshtein_spec.rb +24 -0
  163. data/spec/ofilter/escape_url_spec.rb +50 -0
  164. data/spec/ofilter/footer_spec.rb +32 -0
  165. data/spec/ofilter/geo_spec.rb +33 -0
  166. data/spec/ofilter/short_url_spec.rb +127 -0
  167. data/spec/option_spec.rb +91 -0
  168. data/spec/sized_hash_spec.rb +45 -0
  169. data/spec/spec_helper.rb +35 -0
  170. data/spec/update_checker_spec.rb +55 -0
  171. metadata +326 -0
@@ -0,0 +1,44 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'time'
3
+ require 'atig/command/command'
4
+ require 'atig/command/info'
5
+
6
+ module Atig
7
+ module Command
8
+ class Destroy < Atig::Command::Command
9
+ def initialize(*args); super end
10
+ def command_name; %w(destroy remove rm) end
11
+
12
+ def action(target, mesg, command, args)
13
+ if args.empty?
14
+ yield "/me #{command} <ID1> <ID2> ..."
15
+ return
16
+ end
17
+ args.each do|tid|
18
+ if entry = Info.find_status(db, tid)
19
+ if entry.user.id == db.me.id
20
+ api.delay(0) do|t|
21
+ res = t.post("statuses/destroy/#{entry.status.id}")
22
+ yield "Destroyed: #{entry.status.text}"
23
+
24
+ db.statuses.transaction do|d|
25
+ xs = d.find_by_screen_name db.me.screen_name,:limit=>1
26
+ d.remove_by_id entry.id
27
+ ys = d.find_by_screen_name db.me.screen_name,:limit=>1
28
+
29
+ unless xs.map{|x| x.id} == ys.map{|y| y.id} then
30
+ gateway.topic ys.first
31
+ end
32
+ end
33
+ end
34
+ else
35
+ yield "The status you specified by the ID tid is not yours."
36
+ end
37
+ else
38
+ yield "No such ID tid"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/command/command'
3
+ begin
4
+ require 'jcode'
5
+ rescue LoadError
6
+ end
7
+
8
+ module Atig
9
+ module Command
10
+ class Dm < Atig::Command::Command
11
+ def initialize(*args); super end
12
+ def command_name; %w(d dm dms) end
13
+
14
+ def action(target, mesg, command, args)
15
+ if args.empty?
16
+ yield "/me #{command} <SCREEN_NAME> blah blah"
17
+ return
18
+ end
19
+ user = args.first
20
+ text = mesg.split(" ", 3)[2]
21
+ api.delay(0) do|t|
22
+ t.post("direct_messages/new",{
23
+ :user => user,
24
+ :text => text
25
+ })
26
+ yield "Sent message to #{user}: #{text}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/command/command'
3
+ require 'atig/command/info'
4
+
5
+ module Atig
6
+ module Command
7
+ class Favorite < Atig::Command::Command
8
+ def initialize(*args); super end
9
+ def command_name; %w(fav unfav) end
10
+
11
+ def action(target, mesg, command, args)
12
+ method = { 'fav' => 'create', 'unfav' => 'destroy' }[command]
13
+
14
+ args.each do|tid|
15
+ if entry = Info.find_status(db, tid)
16
+ api.delay(0){|t|
17
+ res = t.post("favorites/#{method}/#{entry.status.id}")
18
+ yield "#{command.upcase}: #{entry.user.screen_name}: #{entry.status.text}"
19
+ }
20
+ else
21
+ yield "No such ID : #{tid}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ module Atig
4
+ module Command
5
+ module Info
6
+ def user(db, api, name, &f)
7
+ if user = db.followings.find_by_screen_name(name) then
8
+ f.call user
9
+ else
10
+ api.delay(0) do|t|
11
+ user = t.get "users/show",:screen_name=>name
12
+ f.call user
13
+ end
14
+ end
15
+ end
16
+
17
+ def status(db, api, id, &f)
18
+ if status = db.statuses.find_by_status_id(id) then
19
+ f.call status
20
+ else
21
+ api.delay(0) do|t|
22
+ status = t.get "statuses/show/#{id}"
23
+ db.statuses.transaction do|d|
24
+ d.add :status => status, :user => status.user, :source => :thread
25
+ f.call d.find_by_status_id(id)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def find_status(db, tid_or_screen_name)
32
+ find = lambda do|x|
33
+ xs = db.statuses.find_by_screen_name(x, :limit=>1)
34
+ unless xs.empty? then
35
+ xs.first
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ (db.statuses.find_by_tid(tid_or_screen_name) ||
42
+ db.statuses.find_by_sid(tid_or_screen_name) ||
43
+ find.call(tid_or_screen_name) ||
44
+ find.call(tid_or_screen_name.sub(/\A@/,'')))
45
+ end
46
+
47
+ module_function :user,:status, :find_status
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,15 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/command/command'
4
+
5
+ module Atig
6
+ module Command
7
+ class Limit < Atig::Command::Command
8
+ def command_name; %w(rls limit limits) end
9
+
10
+ def action(target, mesg, command, args)
11
+ yield "#{api.remain} / #{api.limit} (reset at #{::Time.at(api.reset).strftime('%Y-%m-%d %H:%M:%S')})"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/command/command'
3
+
4
+ module Atig
5
+ module Command
6
+ class Location < Atig::Command::Command
7
+ def command_name; %w(in location loc) end
8
+
9
+ def action(target, mesg, command, args)
10
+ api.delay(0) do|t|
11
+ location = mesg.split(" ", 2)[1] || ""
12
+ t.post('account/update_profile',:location=>location)
13
+
14
+ if location.empty? then
15
+ yield "You are nowhere now."
16
+ else
17
+ yield "You are in #{location} now."
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/command/command'
3
+
4
+ module Atig
5
+ module Command
6
+ class Name < Atig::Command::Command
7
+ def command_name; %w(name) end
8
+
9
+ def action(target, mesg, command, args)
10
+ api.delay(0) do|t|
11
+ name = mesg.split(" ", 2)[1] || ""
12
+ t.post('account/update_profile',:name=>name)
13
+ yield "You are named #{name}."
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/command/command'
4
+ require 'atig/option'
5
+
6
+ module Atig
7
+ module Command
8
+ class Option < Atig::Command::Command
9
+ def initialize(*args)
10
+ super
11
+ @methods = OpenStruct.instance_methods
12
+ end
13
+
14
+ def command_name; %w(opt opts option options) end
15
+
16
+ def action(target, mesg, command, args)
17
+ if args.empty?
18
+ @opts.fields.
19
+ map{|x| x.to_s }.
20
+ sort.each do|name|
21
+ yield "#{name} => #{@opts[name]}"
22
+ end
23
+ else
24
+ _,name,value = mesg.split ' ', 3
25
+ unless value then
26
+ # show the value
27
+ yield "#{name} => #{@opts.send name}"
28
+ else
29
+ # set the value
30
+ @opts.send "#{name}=",::Atig::Option.parse_value(value)
31
+ yield "#{name} => #{@opts.send name}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ # -*- mode:ruby; coding:utf-8 -*-
4
+ require 'atig/command/info'
5
+ require 'time'
6
+ module Atig
7
+ module Command
8
+ class Refresh < Atig::Command::Command
9
+ def command_name; %w(refresh) end
10
+
11
+ def action(target, mesg, command,args)
12
+ db.followings.invalidate
13
+ db.lists.invalidate :all
14
+ yield "refresh followings/lists..."
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/command/command'
3
+ require 'atig/command/info'
4
+
5
+ module Atig
6
+ module Command
7
+ class Reply < Atig::Command::Command
8
+ def initialize(*args); super end
9
+ def command_name; %w(mention re reply rp) end
10
+
11
+ def action(target, mesg, command, args)
12
+ if args.empty?
13
+ yield "/me #{command} <ID_or_SCREEN_NAME> blah blah"
14
+ return
15
+ end
16
+
17
+ tid = args.first
18
+ if entry = Info.find_status(db,tid) then
19
+ text = mesg.split(" ", 3)[2]
20
+ name = entry.user.screen_name
21
+
22
+ text = "@#{name} #{text}" if text.nil? or not text.include?("@#{name}")
23
+
24
+ q = gateway.output_message(:status => text,
25
+ :in_reply_to_status_id => entry.status.id)
26
+
27
+ api.delay(0) do|t|
28
+ ret = t.post("statuses/update", q)
29
+ gateway.update_status ret, target, "In reply to #{name}: #{entry.status.text}"
30
+ end
31
+ else
32
+ yield "No such ID : #{tid}"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/bitly'
4
+ require 'atig/command/command'
5
+ begin
6
+ require 'jcode'
7
+ rescue LoadError
8
+ end
9
+
10
+ module Atig
11
+ module Command
12
+ class Retweet < Atig::Command::Command
13
+ def initialize(*args)
14
+ super
15
+ @bitly = Bitly.no_login @log
16
+ end
17
+
18
+ def command_name; %w(ort rt retweet qt) end
19
+
20
+ def rt_with_comment(target, comment, entry)
21
+ screen_name = "@#{entry.user.screen_name}"
22
+ text = "#{comment.strip} RT #{screen_name}: #{entry.status.text}"
23
+
24
+ chars = text.each_char.to_a
25
+ if chars.size > 140 then
26
+ url = @bitly.shorten "http://twitter.com/#{entry.user.screen_name}/status/#{entry.status.id}"
27
+ text = chars[0,140-url.size-1].join('') + ' ' + url
28
+ end
29
+ q = gateway.output_message(:status => text)
30
+ api.delay(0) do|t|
31
+ ret = t.post("statuses/update", q)
32
+ gateway.update_status ret,target, "RT to #{entry.user.screen_name}: #{entry.status.text}"
33
+ end
34
+ end
35
+
36
+ def rt_with_no_comment(target, entry)
37
+ api.delay(0) do|t|
38
+ ret = t.post("statuses/retweet/#{entry.status.id}")
39
+ gateway.update_status ret,target, "RT to #{entry.user.screen_name}: #{entry.status.text}"
40
+ end
41
+ end
42
+
43
+ def action(target, mesg, command, args)
44
+ if args.empty?
45
+ yield "/me #{command} <ID_or_SCREEN_NAME> blah blah"
46
+ return
47
+ end
48
+
49
+ tid = args.first
50
+ if status = Info.find_status(db, tid) then
51
+ if args.size >= 2
52
+ comment = mesg.split(" ", 3)[2] + " "
53
+ rt_with_comment(target, comment, status)
54
+ else
55
+ rt_with_no_comment(target, status)
56
+ end
57
+ else
58
+ yield "No such ID : #{tid}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode:ruby; coding:utf-8 -*-
3
+ require 'atig/command/command'
4
+ require 'atig/search'
5
+
6
+ # originally developped by xeres
7
+ # http://blog.xeres.jp/2010/06/04/atig_rb-tweet-search/
8
+ module Atig
9
+ module Command
10
+ class Search < Atig::Command::Command
11
+ def command_name; %w(search s) end
12
+
13
+ def action(target, mesg, command, args)
14
+ if args.empty?
15
+ yield "/me #{command} [option...] blah blah"
16
+ return
17
+ end
18
+
19
+ q = mesg.sub(/^#{command}\s+/, '')
20
+ opts = { :q => q }
21
+ while /^:(?:(lang)=(\w+))/ =~ args.first
22
+ opts[$1] = $2
23
+ q.sub!(/^#{args.first}\W+/, "")
24
+ args.shift
25
+ end
26
+
27
+ statuses = api.search.get('search', opts).results
28
+
29
+ if statuses.empty?
30
+ yield "\"#{q}\": not found. options=#{opts.inspect} (#{res['completed_in']} sec.)"
31
+ return
32
+ end
33
+
34
+ statuses.reverse_each do|status|
35
+ db.statuses.transaction do|d|
36
+ user = TwitterStruct.make('id' => status.from_user_id,
37
+ 'screen_name' => status.from_user)
38
+ d.add :status => status, :user => user, :source => :user
39
+ end
40
+ end
41
+
42
+ statuses.reverse_each do|status|
43
+ entry = db.statuses.find_by_status_id(status.id)
44
+ entry.status = entry.status.merge('text' =>
45
+ "#{entry.status.text} (#{entry.status.created_at})")
46
+ gateway[target].message entry, Net::IRC::Constants::NOTICE
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/command/command'
3
+ require 'atig/command/info'
4
+
5
+ module Atig
6
+ module Command
7
+ class Spam < Atig::Command::Command
8
+ def initialize(*args); super end
9
+ def command_name; %w(spam SPAM) end
10
+
11
+ def action(target, mesg, command, args)
12
+ if args.empty?
13
+ yield "/me #{command} <SCREEN_NAME1> <SCREEN_NAME2> ..."
14
+ return
15
+ else
16
+ args.each do|screen_name|
17
+ api.delay(0) do|t|
18
+ res = t.post("report_spam",:screen_name => screen_name)
19
+ yield "Report #{res.screen_name} as SPAMMER"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end