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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +481 -0
- data/Rakefile +10 -0
- data/bin/console +42 -0
- data/birdwatcher.gemspec +40 -0
- data/data/english_stopwords.txt +319 -0
- data/data/top100Kenglishwords.txt +100000 -0
- data/db/migrations/001_create_workspaces.rb +11 -0
- data/db/migrations/002_create_users.rb +29 -0
- data/db/migrations/003_create_statuses.rb +28 -0
- data/db/migrations/004_create_mentions.rb +13 -0
- data/db/migrations/005_create_mentions_statuses.rb +8 -0
- data/db/migrations/006_create_hashtags.rb +11 -0
- data/db/migrations/007_create_hashtags_statuses.rb +8 -0
- data/db/migrations/008_create_urls.rb +16 -0
- data/db/migrations/009_create_statuses_urls.rb +8 -0
- data/db/migrations/010_create_klout_topics.rb +10 -0
- data/db/migrations/011_create_klout_topics_users.rb +8 -0
- data/db/migrations/012_create_influencers.rb +10 -0
- data/db/migrations/013_create_influencers_users.rb +8 -0
- data/db/migrations/014_create_influencees.rb +10 -0
- data/db/migrations/015_create_influencees_users.rb +8 -0
- data/exe/birdwatcher +12 -0
- data/lib/birdwatcher/command.rb +78 -0
- data/lib/birdwatcher/commands/back.rb +15 -0
- data/lib/birdwatcher/commands/exit.rb +16 -0
- data/lib/birdwatcher/commands/help.rb +60 -0
- data/lib/birdwatcher/commands/irb.rb +34 -0
- data/lib/birdwatcher/commands/module.rb +106 -0
- data/lib/birdwatcher/commands/query.rb +58 -0
- data/lib/birdwatcher/commands/query_csv.rb +56 -0
- data/lib/birdwatcher/commands/resource.rb +45 -0
- data/lib/birdwatcher/commands/run.rb +19 -0
- data/lib/birdwatcher/commands/schema.rb +116 -0
- data/lib/birdwatcher/commands/set.rb +56 -0
- data/lib/birdwatcher/commands/shell.rb +21 -0
- data/lib/birdwatcher/commands/show.rb +86 -0
- data/lib/birdwatcher/commands/status.rb +114 -0
- data/lib/birdwatcher/commands/unset.rb +37 -0
- data/lib/birdwatcher/commands/use.rb +25 -0
- data/lib/birdwatcher/commands/user.rb +155 -0
- data/lib/birdwatcher/commands/workspace.rb +176 -0
- data/lib/birdwatcher/concerns/concurrency.rb +25 -0
- data/lib/birdwatcher/concerns/core.rb +105 -0
- data/lib/birdwatcher/concerns/outputting.rb +114 -0
- data/lib/birdwatcher/concerns/persistence.rb +101 -0
- data/lib/birdwatcher/concerns/presentation.rb +122 -0
- data/lib/birdwatcher/concerns/util.rb +138 -0
- data/lib/birdwatcher/configuration.rb +63 -0
- data/lib/birdwatcher/configuration_wizard.rb +65 -0
- data/lib/birdwatcher/console.rb +201 -0
- data/lib/birdwatcher/http_client.rb +164 -0
- data/lib/birdwatcher/klout_client.rb +83 -0
- data/lib/birdwatcher/kml.rb +125 -0
- data/lib/birdwatcher/module.rb +253 -0
- data/lib/birdwatcher/modules/statuses/kml.rb +106 -0
- data/lib/birdwatcher/modules/statuses/sentiment.rb +77 -0
- data/lib/birdwatcher/modules/statuses/word_cloud.rb +205 -0
- data/lib/birdwatcher/modules/urls/crawl.rb +138 -0
- data/lib/birdwatcher/modules/urls/most_shared.rb +98 -0
- data/lib/birdwatcher/modules/users/activity_plot.rb +62 -0
- data/lib/birdwatcher/modules/users/import.rb +61 -0
- data/lib/birdwatcher/modules/users/influence_graph.rb +93 -0
- data/lib/birdwatcher/modules/users/klout_id.rb +62 -0
- data/lib/birdwatcher/modules/users/klout_influence.rb +83 -0
- data/lib/birdwatcher/modules/users/klout_score.rb +64 -0
- data/lib/birdwatcher/modules/users/klout_topics.rb +72 -0
- data/lib/birdwatcher/modules/users/social_graph.rb +110 -0
- data/lib/birdwatcher/punchcard.rb +183 -0
- data/lib/birdwatcher/util.rb +83 -0
- data/lib/birdwatcher/version.rb +3 -0
- data/lib/birdwatcher.rb +43 -0
- data/models/hashtag.rb +8 -0
- data/models/influencee.rb +8 -0
- data/models/influencer.rb +8 -0
- data/models/klout_topic.rb +8 -0
- data/models/mention.rb +8 -0
- data/models/status.rb +11 -0
- data/models/url.rb +8 -0
- data/models/user.rb +11 -0
- data/models/workspace.rb +26 -0
- 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
|