birdwatcher 0.1.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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +481 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +42 -0
  9. data/birdwatcher.gemspec +40 -0
  10. data/data/english_stopwords.txt +319 -0
  11. data/data/top100Kenglishwords.txt +100000 -0
  12. data/db/migrations/001_create_workspaces.rb +11 -0
  13. data/db/migrations/002_create_users.rb +29 -0
  14. data/db/migrations/003_create_statuses.rb +28 -0
  15. data/db/migrations/004_create_mentions.rb +13 -0
  16. data/db/migrations/005_create_mentions_statuses.rb +8 -0
  17. data/db/migrations/006_create_hashtags.rb +11 -0
  18. data/db/migrations/007_create_hashtags_statuses.rb +8 -0
  19. data/db/migrations/008_create_urls.rb +16 -0
  20. data/db/migrations/009_create_statuses_urls.rb +8 -0
  21. data/db/migrations/010_create_klout_topics.rb +10 -0
  22. data/db/migrations/011_create_klout_topics_users.rb +8 -0
  23. data/db/migrations/012_create_influencers.rb +10 -0
  24. data/db/migrations/013_create_influencers_users.rb +8 -0
  25. data/db/migrations/014_create_influencees.rb +10 -0
  26. data/db/migrations/015_create_influencees_users.rb +8 -0
  27. data/exe/birdwatcher +12 -0
  28. data/lib/birdwatcher/command.rb +78 -0
  29. data/lib/birdwatcher/commands/back.rb +15 -0
  30. data/lib/birdwatcher/commands/exit.rb +16 -0
  31. data/lib/birdwatcher/commands/help.rb +60 -0
  32. data/lib/birdwatcher/commands/irb.rb +34 -0
  33. data/lib/birdwatcher/commands/module.rb +106 -0
  34. data/lib/birdwatcher/commands/query.rb +58 -0
  35. data/lib/birdwatcher/commands/query_csv.rb +56 -0
  36. data/lib/birdwatcher/commands/resource.rb +45 -0
  37. data/lib/birdwatcher/commands/run.rb +19 -0
  38. data/lib/birdwatcher/commands/schema.rb +116 -0
  39. data/lib/birdwatcher/commands/set.rb +56 -0
  40. data/lib/birdwatcher/commands/shell.rb +21 -0
  41. data/lib/birdwatcher/commands/show.rb +86 -0
  42. data/lib/birdwatcher/commands/status.rb +114 -0
  43. data/lib/birdwatcher/commands/unset.rb +37 -0
  44. data/lib/birdwatcher/commands/use.rb +25 -0
  45. data/lib/birdwatcher/commands/user.rb +155 -0
  46. data/lib/birdwatcher/commands/workspace.rb +176 -0
  47. data/lib/birdwatcher/concerns/concurrency.rb +25 -0
  48. data/lib/birdwatcher/concerns/core.rb +105 -0
  49. data/lib/birdwatcher/concerns/outputting.rb +114 -0
  50. data/lib/birdwatcher/concerns/persistence.rb +101 -0
  51. data/lib/birdwatcher/concerns/presentation.rb +122 -0
  52. data/lib/birdwatcher/concerns/util.rb +138 -0
  53. data/lib/birdwatcher/configuration.rb +63 -0
  54. data/lib/birdwatcher/configuration_wizard.rb +65 -0
  55. data/lib/birdwatcher/console.rb +201 -0
  56. data/lib/birdwatcher/http_client.rb +164 -0
  57. data/lib/birdwatcher/klout_client.rb +83 -0
  58. data/lib/birdwatcher/kml.rb +125 -0
  59. data/lib/birdwatcher/module.rb +253 -0
  60. data/lib/birdwatcher/modules/statuses/kml.rb +106 -0
  61. data/lib/birdwatcher/modules/statuses/sentiment.rb +77 -0
  62. data/lib/birdwatcher/modules/statuses/word_cloud.rb +205 -0
  63. data/lib/birdwatcher/modules/urls/crawl.rb +138 -0
  64. data/lib/birdwatcher/modules/urls/most_shared.rb +98 -0
  65. data/lib/birdwatcher/modules/users/activity_plot.rb +62 -0
  66. data/lib/birdwatcher/modules/users/import.rb +61 -0
  67. data/lib/birdwatcher/modules/users/influence_graph.rb +93 -0
  68. data/lib/birdwatcher/modules/users/klout_id.rb +62 -0
  69. data/lib/birdwatcher/modules/users/klout_influence.rb +83 -0
  70. data/lib/birdwatcher/modules/users/klout_score.rb +64 -0
  71. data/lib/birdwatcher/modules/users/klout_topics.rb +72 -0
  72. data/lib/birdwatcher/modules/users/social_graph.rb +110 -0
  73. data/lib/birdwatcher/punchcard.rb +183 -0
  74. data/lib/birdwatcher/util.rb +83 -0
  75. data/lib/birdwatcher/version.rb +3 -0
  76. data/lib/birdwatcher.rb +43 -0
  77. data/models/hashtag.rb +8 -0
  78. data/models/influencee.rb +8 -0
  79. data/models/influencer.rb +8 -0
  80. data/models/klout_topic.rb +8 -0
  81. data/models/mention.rb +8 -0
  82. data/models/status.rb +11 -0
  83. data/models/url.rb +8 -0
  84. data/models/user.rb +11 -0
  85. data/models/workspace.rb +26 -0
  86. metadata +405 -0
@@ -0,0 +1,45 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Resource < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Execute commands from a resource file",
6
+ :names => %w(resource),
7
+ :usage => "resource FILE"
8
+ }
9
+
10
+ def self.detailed_usage
11
+ <<-USAGE
12
+ The #{'resource'.bold} command can be used to execute commands from a file on disk.
13
+ Resource files are simple text-based files containing one command per line. They
14
+ can be very convenient for common or repetitive workflows.
15
+
16
+ #{'USAGE:'.bold}
17
+
18
+ #{'Execute commands from a resource file:'.bold}
19
+ resource <FILE>
20
+ USAGE
21
+ end
22
+
23
+ def run
24
+ if !arguments?
25
+ error("You must provide a path to a resource file")
26
+ return false
27
+ end
28
+ filepath = File.expand_path(arguments.join(" "))
29
+ if !File.exists?(filepath)
30
+ error("File #{filepath.bold} does not exist")
31
+ return false
32
+ end
33
+ if !File.readable?(filepath)
34
+ error("File #{filepath} is not readable")
35
+ return false
36
+ end
37
+ File.read(filepath).each_line do |command|
38
+ command.strip!
39
+ next if command.empty? || command.start_with?("#") || command.start_with?("//")
40
+ console.handle_input(command.strip)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Run < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Run current module",
6
+ :names => %w(run execute),
7
+ :usage => "run"
8
+ }
9
+
10
+ def run
11
+ if !console.current_module
12
+ error("No module loaded")
13
+ return false
14
+ end
15
+ console.current_module.new.execute
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,116 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Schema < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Show schema for database table",
6
+ :names => %w(schema table),
7
+ :usage => "schema [TABLE_NAME]"
8
+ }
9
+
10
+ def self.detailed_usage
11
+ <<-USAGE
12
+ The #{'schema'.bold} command can be used to get schema information on tables in
13
+ the database.
14
+
15
+ #{'USAGE:'.bold}
16
+
17
+ #{'List all available tables:'.bold}
18
+ schema
19
+
20
+ #{'See columns, indexes and foreign keys on a table:'.bold}
21
+ schema statuses
22
+ USAGE
23
+ end
24
+
25
+ def run
26
+ if !arguments?
27
+ tables = database.tables.sort
28
+ output_available_tables(tables)
29
+ return
30
+ end
31
+ table = arguments.first.strip
32
+ tables = database.tables.sort.map(&:to_s)
33
+ if !tables.include?(table)
34
+ error("Unknown table: #{table.bold}")
35
+ newline
36
+ output_available_tables(tables)
37
+ return false
38
+ end
39
+ schema = database.schema(table.to_sym)
40
+ indexes = database.indexes(table.to_sym)
41
+ foreign_keys = database.foreign_key_list(table.to_sym)
42
+ info("Schema information for table #{table.bold}:")
43
+ newline
44
+ output_schema_table(schema)
45
+ newline
46
+ info("Indexes on table #{table.bold}:")
47
+ newline
48
+ output_index_table(indexes)
49
+ newline
50
+ info("Foreign keys on table #{table.bold}:")
51
+ newline
52
+ output_foreign_key_table(foreign_keys)
53
+ newline
54
+ end
55
+
56
+ private
57
+
58
+ def output_available_tables(tables)
59
+ info("Available tables:")
60
+ newline
61
+ tables.each do |table|
62
+ output " * #{table.to_s.bold}"
63
+ end
64
+ newline
65
+ end
66
+
67
+ def output_schema_table(schema)
68
+ table_rows = []
69
+ schema.each do |column|
70
+ name, attr = column
71
+ table_rows << [
72
+ name,
73
+ attr[:db_type],
74
+ (attr[:default] ? attr[:default] : "NULL"),
75
+ (attr[:allow_null] ? "Yes" : "No"),
76
+ (attr[:primary_key] ? "Yes" : "No"),
77
+ ]
78
+ end
79
+ output Terminal::Table.new(
80
+ :headings => ["Column Name", "Type", "Default", "Allow NULL", "Primary Key"].map(&:bold),
81
+ :rows => table_rows
82
+ )
83
+ end
84
+
85
+ def output_index_table(indexes)
86
+ table_rows = []
87
+ indexes.each_pair do |name, index|
88
+ table_rows << [
89
+ name.to_s,
90
+ index[:columns].map(&:to_s).join(", "),
91
+ (index[:unique] ? "Yes" : "No")
92
+ ]
93
+ end
94
+ output Terminal::Table.new(
95
+ :headings => ["Index Name", "Column(s)", "Unique"].map(&:bold),
96
+ :rows => table_rows
97
+ )
98
+ end
99
+
100
+ def output_foreign_key_table(foreign_keys)
101
+ table_rows = []
102
+ foreign_keys.each do |foreign_key|
103
+ table_rows << [
104
+ foreign_key[:columns].map(&:to_s).join(", "),
105
+ foreign_key[:table].to_s,
106
+ foreign_key[:key].map(&:to_s).join(", ")
107
+ ]
108
+ end
109
+ output Terminal::Table.new(
110
+ :headings => ["Column(s)", "Referenced Table", "Referenced Column(s)"].map(&:bold),
111
+ :rows => table_rows
112
+ )
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,56 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Set < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Set module option",
6
+ :names => %w(set),
7
+ :usage => "set OPTION VALUE"
8
+ }
9
+
10
+ TRUTHY_VALUES = %w(1 true yes on).freeze
11
+ FALSY_VALUES = %w(0 false no off).freeze
12
+
13
+ def run
14
+ if arguments.count < 2
15
+ error("You must provide an option name and value")
16
+ return false
17
+ end
18
+
19
+ if !current_module
20
+ error("No module loaded")
21
+ return false
22
+ end
23
+
24
+ option, value = arguments.first.upcase, arguments[1..-1].join(" ")
25
+ if !current_module.meta[:options].keys.include?(option)
26
+ error("Unknown option: #{option.bold}")
27
+ return false
28
+ end
29
+
30
+ if current_module.meta[:options][option][:boolean]
31
+ if truthy?(value)
32
+ value = true
33
+ elsif falsy?(value)
34
+ value = false
35
+ end
36
+ end
37
+
38
+ current_module.meta[:options][option][:value] = value
39
+ end
40
+
41
+ private
42
+
43
+ def truthy?(value)
44
+ TRUTHY_VALUES.include?(value.downcase)
45
+ end
46
+
47
+ def falsy?(value)
48
+ FALSY_VALUES.include?(value.downcase)
49
+ end
50
+
51
+ def current_module
52
+ console.current_module
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Shell < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Execute shell command",
6
+ :names => %w(shell),
7
+ :usage => "shell COMMAND"
8
+ }
9
+
10
+ def run
11
+ if !arguments?
12
+ error("You must provide a shell command to execute")
13
+ return false
14
+ end
15
+
16
+ command = arguments.join(" ")
17
+ output `#{command}`
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,86 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Show < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Show module details and options",
6
+ :names => %w(show),
7
+ :usage => "show DETAILS"
8
+ }
9
+
10
+ def self.detailed_usage
11
+ <<-USAGE
12
+ The #{'show'.bold} command shows information and options on Birdwatcher
13
+ modules.
14
+
15
+ #{'USAGE:'.bold}
16
+
17
+ #{'See available module options:'.bold}
18
+ show options
19
+
20
+ #{'See additional module information:'.bold}
21
+ show info
22
+ USAGE
23
+ end
24
+
25
+ def run
26
+ if !arguments?
27
+ error("You must specify what to see")
28
+ return false
29
+ end
30
+
31
+ if !current_module
32
+ error("No module loaded")
33
+ return false
34
+ end
35
+
36
+ case arguments.first
37
+ when "info", "description", "details"
38
+ output_info
39
+ when "options", "opts"
40
+ output_options
41
+ else
42
+ error("Don't know how to show #{arguments.first.bold}")
43
+ return false
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def output_info
50
+ newline
51
+ output " Name: ".bold + current_module.meta[:name]
52
+ output "Description: ".bold + current_module.meta[:description]
53
+ output " Author: ".bold + current_module.meta[:author]
54
+ output " Path: ".bold + current_module.path
55
+ newline
56
+ line_separator
57
+ newline
58
+ if current_module.info
59
+ output current_module.info
60
+ else
61
+ info("No further information has been provided for this module")
62
+ end
63
+ newline
64
+ end
65
+
66
+ def output_options
67
+ if current_module.meta[:options].empty?
68
+ info("This module has no options")
69
+ return
70
+ end
71
+ table = Terminal::Table.new(:headings => ["Name", "Current Setting", "Required", "Description"].map(&:bold))
72
+ table.style = { :border_y => "", :border_i => "" }
73
+ current_module.meta[:options].each_pair do |key, value|
74
+ table.add_row([key, value[:value], (value[:required] ? 'yes' : 'no'), value[:description]])
75
+ end
76
+ newline
77
+ output table
78
+ newline
79
+ end
80
+
81
+ def current_module
82
+ console.current_module
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,114 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Status < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Manage statuses",
6
+ :names => %w(status statuses tweet tweets),
7
+ :usage => "status [ACTION]"
8
+ }
9
+
10
+ def self.detailed_usage
11
+ <<-USAGE
12
+ The #{'status'.bold} command manipulates statuses in the current workspace.
13
+
14
+ #{'USAGE:'.bold}
15
+
16
+ #{'List the last 1000 statuses from all users:'.bold}
17
+ status list
18
+
19
+ #{'List the last 1000 statuses from specific users:'.bold}
20
+ status list [USER1[ USER2 ... USERN]]
21
+
22
+ #{'Fetch and process new statuses from all users:'.bold}
23
+ status fetch
24
+
25
+ #{'Search all statuses for a specific word:'.bold}
26
+ status search SEARCHTERM
27
+ USAGE
28
+ end
29
+
30
+ def run
31
+ if !arguments?
32
+ error("You must provide an action")
33
+ return false
34
+ end
35
+ action = arguments.first.downcase
36
+ case action
37
+ when "list", "-l", "show", "-s"
38
+ list_statuses(arguments[1..-1])
39
+ when "search", "-s", "find"
40
+ search_statuses
41
+ when "fetch", "-f", "update", "-u"
42
+ fetch_statuses
43
+ else
44
+ list_statuses(arguments)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def fetch_statuses
51
+ current_workspace.users_dataset.order(:screen_name).each do |user|
52
+ statuses = []
53
+ task("Fetching statuses for #{user.screen_name.bold}...") do
54
+ statuses = get_statuses_for_user(user)
55
+ end
56
+ task("Processing #{Birdwatcher::Util.pluralize(statuses.count, 'status', 'statuses')}...") do
57
+ threads = thread_pool
58
+ statuses.each do |status|
59
+ threads.process do
60
+ save_status(status, user)
61
+ end
62
+ end
63
+ threads.shutdown
64
+ end
65
+ end
66
+ end
67
+
68
+ def search_statuses
69
+ search_term = arguments[1..-1].join(" ")
70
+ if search_term.empty?
71
+ error("You must provide a search term")
72
+ return false
73
+ end
74
+ statuses = current_workspace.statuses_dataset
75
+ .where("text LIKE ?", "%#{search_term}%")
76
+ .order(Sequel.desc(:posted_at))
77
+ .eager(:user)
78
+ .limit(1000)
79
+ texts = statuses.map { |s| make_status_summary_output(s) }.join("\n#{Birdwatcher::Console::LINE_SEPARATOR}\n\n")
80
+ page_text(texts)
81
+ end
82
+
83
+ def list_statuses(screen_names = nil)
84
+ if screen_names.nil? || screen_names.empty?
85
+ statuses = current_workspace.statuses_dataset.order(Sequel.desc(:posted_at)).eager(:user).limit(1000)
86
+ else
87
+ user_ids = current_workspace.users_dataset.where("screen_name IN ?", screen_names).map(:id)
88
+ statuses = current_workspace.statuses_dataset.where("user_id IN ?", user_ids).order(Sequel.desc(:posted_at)).eager(:user).limit(1000)
89
+ end
90
+ texts = statuses.map { |s| make_status_summary_output(s) }.join("\n#{Birdwatcher::Console::LINE_SEPARATOR}\n\n")
91
+ page_text(texts)
92
+ end
93
+
94
+ def collect_with_max_id(collection = [], max_id = nil, pages = 5, &block)
95
+ return collection.flatten if pages.zero?
96
+ response = yield(max_id)
97
+ collection += response
98
+ response.empty? ? collection.flatten : collect_with_max_id(collection, response.last.id - 1, pages - 1, &block)
99
+ end
100
+
101
+ def get_statuses_for_user(user)
102
+ if last_status = user.statuses_dataset.order(Sequel.desc(:posted_at)).first
103
+ since_id = last_status.twitter_id
104
+ end
105
+ collect_with_max_id do |max_id|
106
+ options = {:count => 200, :include_rts => true}
107
+ options[:since_id] = since_id unless since_id.nil?
108
+ options[:max_id] = max_id unless max_id.nil?
109
+ twitter_client.user_timeline(user.screen_name, options)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,37 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Unset < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Unset module option",
6
+ :names => %w(unset),
7
+ :usage => "unset OPTION"
8
+ }
9
+
10
+ def run
11
+ if !arguments?
12
+ error("You must provide an option name")
13
+ return false
14
+ end
15
+
16
+ if !current_module
17
+ error("No module loaded")
18
+ return false
19
+ end
20
+
21
+ option = arguments.first.upcase
22
+ if !current_module.meta[:options].keys.include?(option)
23
+ error("Unknown option: #{option.bold}")
24
+ return false
25
+ end
26
+
27
+ current_module.meta[:options][option][:value] = nil
28
+ end
29
+
30
+ private
31
+
32
+ def current_module
33
+ console.current_module
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class Use < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Load specified module",
6
+ :names => %w(use load),
7
+ :usage => "use MODULE_PATH"
8
+ }
9
+
10
+ def run
11
+ if !arguments?
12
+ error("You must provide a module path")
13
+ return false
14
+ end
15
+
16
+ if !_module = Birdwatcher::Module.module_by_path(arguments.first)
17
+ error("Unknown module: #{arguments.first.bold}")
18
+ return false
19
+ end
20
+
21
+ console.current_module = _module
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,155 @@
1
+ module Birdwatcher
2
+ module Commands
3
+ class User < Birdwatcher::Command
4
+ self.meta = {
5
+ :description => "Manage users",
6
+ :names => %w(user users),
7
+ :usage => "user [ACTION]"
8
+ }
9
+
10
+ def self.detailed_usage
11
+ <<-USAGE
12
+ The #{'user'.bold} command manipulates users in the current workspace.
13
+
14
+ #{'USAGE:'.bold}
15
+
16
+ #{'List all users in the workspace:'.bold}
17
+ user list
18
+
19
+ #{'Add one or more users to the workspace:'.bold}
20
+ user create [USER1[ USER2 ... USERN]]
21
+
22
+ #{'Update information on one or more users:'.bold}
23
+ user update [USER1[ USER2 ... USERN]]
24
+
25
+ #{'Delete one or more users from the workspace:'.bold}
26
+ user delete [USER1[ USER2 ... USERN]]
27
+ USAGE
28
+ end
29
+
30
+ def run
31
+ if !arguments?
32
+ error("You must provide an action")
33
+ return false
34
+ end
35
+ action = arguments.first.downcase
36
+ case action
37
+ when "list"
38
+ list_users
39
+ when "create", "add", "-a"
40
+ create_users
41
+ when "update", "-u"
42
+ update_users
43
+ when "delete", "destroy", "rm", "-d"
44
+ delete_users
45
+ else
46
+ list_user
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def list_users
53
+ if current_workspace.users.count.zero?
54
+ info("There are currently no users in this workspace")
55
+ return true
56
+ end
57
+ newline
58
+ users = current_workspace.users_dataset.order(:screen_name).map do |u|
59
+ make_user_summary_output(u)
60
+ end.join("\n#{Birdwatcher::Console::LINE_SEPARATOR}\n\n")
61
+ page_text(users)
62
+ end
63
+
64
+ def list_user
65
+ if !user = current_workspace.users_dataset.first(:screen_name => arguments.first)
66
+ error("User #{screen_name.bold} was not found in workspace")
67
+ return false
68
+ end
69
+ newline
70
+ output_user_details(user)
71
+ newline
72
+ end
73
+
74
+ def create_users
75
+ screen_names = arguments[1..-1].uniq
76
+ if screen_names.empty?
77
+ error("You must provide at least one screen name")
78
+ return false
79
+ end
80
+ screen_names.each do |screen_name|
81
+ begin
82
+ if current_workspace.users_dataset.first(:screen_name => screen_name)
83
+ error("User #{screen_name.bold} is already in the workspace")
84
+ next
85
+ end
86
+ api_user = twitter_client.user(screen_name)
87
+ save_user(api_user)
88
+ info("Added #{screen_name.bold} to workspace")
89
+ rescue Twitter::Error::NotFound
90
+ error("There is no user with screen name: #{screen_name.bold}")
91
+ end
92
+ end
93
+ end
94
+
95
+ def update_users
96
+ screen_names = arguments[1..-1].uniq
97
+ if screen_names.empty?
98
+ db_users = current_workspace.users
99
+ else
100
+ db_users = current_workspace.users_dataset.where("screen_name IN ?", screen_names)
101
+ end
102
+ threads = thread_pool
103
+ db_users.each do |db_user|
104
+ threads.process do
105
+ begin
106
+ api_user = twitter_client.user(db_user.screen_name)
107
+ db_user.update(
108
+ :name => api_user.name,
109
+ :location => api_user.location,
110
+ :description => api_user.description,
111
+ :url => (api_user.website_urls.first ? api_user.website_urls.first.expanded_url.to_s : nil),
112
+ :profile_image_url => api_user.profile_image_url_https.to_s,
113
+ :followers_count => api_user.followers_count,
114
+ :friends_count => api_user.friends_count,
115
+ :listed_count => api_user.listed_count,
116
+ :favorites_count => api_user.favorites_count,
117
+ :statuses_count => api_user.statuses_count,
118
+ :utc_offset => api_user.utc_offset,
119
+ :timezone => api_user.time_zone,
120
+ :geo_enabled => api_user.geo_enabled?,
121
+ :verified => api_user.verified?,
122
+ :lang => api_user.lang
123
+ )
124
+ info("Updated information for #{db_user.screen_name.bold}")
125
+ rescue Twitter::Error::NotFound
126
+ error("There is no user with screen name: #{db_user.screen_name.bold}")
127
+ end
128
+ end
129
+ end
130
+ threads.shutdown
131
+ end
132
+
133
+ def delete_users
134
+ screen_names = arguments[1..-1].uniq
135
+ if screen_names.empty?
136
+ error("You must provide at least one screen name")
137
+ return false
138
+ end
139
+ return unless confirm("Are you sure you want to delete #{screen_names.map(&:bold).join(', ')} and all associated data?")
140
+ screen_names.each do |screen_name|
141
+ begin
142
+ if !db_user = current_workspace.users_dataset.first(:screen_name => screen_name)
143
+ error("User #{screen_name.bold} was not found in workspace")
144
+ next
145
+ end
146
+ db_user.destroy
147
+ info("Deleted #{screen_name.bold} from workspace")
148
+ rescue Twitter::Error::NotFound
149
+ error("There is no user with screen name: #{screen_name.bold}")
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end