birdwatcher 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,176 @@
|
|
1
|
+
module Birdwatcher
|
2
|
+
module Commands
|
3
|
+
class Workspace < Birdwatcher::Command
|
4
|
+
self.meta = {
|
5
|
+
:description => "Manage workspaces",
|
6
|
+
:names => %w(workspace workspaces),
|
7
|
+
:usage => "workspace [ACTION]"
|
8
|
+
}
|
9
|
+
|
10
|
+
def self.detailed_usage
|
11
|
+
<<-USAGE
|
12
|
+
Workspaces enable you to segment and manage users and data stored in the database.
|
13
|
+
You can use workspaces to create logical separation between different users.
|
14
|
+
For example, you may want to create a workspace for a company, a department or
|
15
|
+
for a specific topic.
|
16
|
+
|
17
|
+
There will always be a default workspace with the name #{Birdwatcher::Models::Workspace::DEFAULT_WORKSPACE_NAME.bold} which might be enough
|
18
|
+
if you plan to use Birdwatcher for a small group of Twitter users.
|
19
|
+
|
20
|
+
#{'USAGE:'.bold}
|
21
|
+
|
22
|
+
#{'List available workspaces:'.bold}
|
23
|
+
workspace list
|
24
|
+
|
25
|
+
#{'Create a new workspace:'.bold}
|
26
|
+
workspace create NAME [DESCRIPTION]
|
27
|
+
|
28
|
+
#{'Switch to a workspace:'.bold}
|
29
|
+
workspace use NAME
|
30
|
+
|
31
|
+
#{'Delete a workspace:'.bold}
|
32
|
+
workspace delete NAME
|
33
|
+
|
34
|
+
#{'Rename a workspace'.bold}
|
35
|
+
workspace rename NAME NEW_NAME
|
36
|
+
USAGE
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
if !arguments?
|
41
|
+
info("Current workspace: #{current_workspace.name.bold} (database ID: #{current_workspace.id.to_s.bold})")
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
action = arguments.first.downcase
|
45
|
+
case action
|
46
|
+
when "list"
|
47
|
+
list_workspaces
|
48
|
+
when "create", "add", "-a"
|
49
|
+
create_workspace
|
50
|
+
when "rename", "-r"
|
51
|
+
rename_workspace
|
52
|
+
when "select", "use"
|
53
|
+
select_workspace
|
54
|
+
when "delete", "destroy", "rm", "-d"
|
55
|
+
delete_workspace
|
56
|
+
else
|
57
|
+
select_workspace(arguments.first)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def list_workspaces
|
62
|
+
longest_workspace_name = Birdwatcher::Models::Workspace.all.map(&:name).max_by(&:length)
|
63
|
+
info("Available workspaces:\n")
|
64
|
+
Birdwatcher::Models::Workspace.order(:name).each do |workspace|
|
65
|
+
if current_workspace.id == workspace.id
|
66
|
+
workspace_name = "*".bold.light_green + " #{workspace.name}"
|
67
|
+
else
|
68
|
+
workspace_name = " #{workspace.name}"
|
69
|
+
end
|
70
|
+
|
71
|
+
output_formatted(" %-#{longest_workspace_name.bold.length}s \t\t%s\n", workspace_name.bold, workspace.description)
|
72
|
+
end
|
73
|
+
newline
|
74
|
+
end
|
75
|
+
|
76
|
+
def select_workspace(name = nil)
|
77
|
+
name ||= arguments[1]
|
78
|
+
|
79
|
+
if !name
|
80
|
+
error("You must provide a workspace name")
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
|
84
|
+
if workspace = Birdwatcher::Models::Workspace.first(:name => name)
|
85
|
+
self.current_workspace = workspace
|
86
|
+
info("Now using workspace: #{workspace.name.bold}")
|
87
|
+
else
|
88
|
+
error("There is no workspace with that name")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def create_workspace
|
93
|
+
name = arguments[1]
|
94
|
+
description = arguments[2..-1].to_a.join(" ")
|
95
|
+
description = nil unless description
|
96
|
+
|
97
|
+
if !name
|
98
|
+
error("You must provide a workspace name")
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
if Birdwatcher::Models::Workspace.first(:name => name)
|
103
|
+
error("There is already a workspace with that name")
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
|
107
|
+
workspace = Birdwatcher::Models::Workspace.create(
|
108
|
+
:name => name,
|
109
|
+
:description => description
|
110
|
+
)
|
111
|
+
|
112
|
+
info("Created workspace: #{workspace.name.bold}")
|
113
|
+
self.current_workspace = workspace
|
114
|
+
end
|
115
|
+
|
116
|
+
def rename_workspace
|
117
|
+
old_name = arguments[1]
|
118
|
+
new_name = arguments[2]
|
119
|
+
|
120
|
+
if !old_name || !new_name
|
121
|
+
error("You must provide workspace name and new name")
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
|
125
|
+
if old_name == Birdwatcher::Models::Workspace::DEFAULT_WORKSPACE_NAME
|
126
|
+
error("Default workspace cannot be renamed")
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
|
130
|
+
if !old_workspace = Birdwatcher::Models::Workspace.first(:name => old_name)
|
131
|
+
error("There is no workspace named #{old_name.bold}")
|
132
|
+
return false
|
133
|
+
end
|
134
|
+
|
135
|
+
if Birdwatcher::Models::Workspace.first(:name => new_name)
|
136
|
+
error("There is already a workspace named #{new_name.bold}")
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
|
140
|
+
old_workspace.update(:name => new_name)
|
141
|
+
if old_workspace.id == current_workspace.id
|
142
|
+
self.current_workspace = old_workspace
|
143
|
+
end
|
144
|
+
|
145
|
+
info("Workspace #{old_name.bold} renamed to #{new_name.bold}")
|
146
|
+
end
|
147
|
+
|
148
|
+
def delete_workspace
|
149
|
+
name = arguments[1]
|
150
|
+
|
151
|
+
if !name
|
152
|
+
error("You must provide a workspace name")
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
|
156
|
+
if workspace = Birdwatcher::Models::Workspace.first(:name => name)
|
157
|
+
return unless confirm("Are you sure you want to delete #{name.bold} and all associated data?")
|
158
|
+
workspace.destroy
|
159
|
+
info("Deleted workspace: #{workspace.name.bold}")
|
160
|
+
if workspace.default_workspace?
|
161
|
+
self.current_workspace = Birdwatcher::Models::Workspace.create_default_workspace!
|
162
|
+
return
|
163
|
+
end
|
164
|
+
if current_workspace.id == workspace.id
|
165
|
+
self.current_workspace = Birdwatcher::Models::Workspace.first(
|
166
|
+
:name => Birdwatcher::Models::Workspace::DEFAULT_WORKSPACE_NAME
|
167
|
+
)
|
168
|
+
end
|
169
|
+
else
|
170
|
+
error("There is no workspace with that name")
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Birdwatcher
|
2
|
+
module Concerns
|
3
|
+
module Concurrency
|
4
|
+
# The default size of thread pool
|
5
|
+
# @private
|
6
|
+
DEFAULT_THREAD_POOL_SIZE = 10.freeze
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
# Create a new thread pool
|
16
|
+
#
|
17
|
+
# @param size [Integer] OPTIONAL: The size of the thread pool (default size if not specified)
|
18
|
+
# @return [Thread::Pool]
|
19
|
+
# @see https://github.com/meh/ruby-thread#pool
|
20
|
+
def thread_pool(size = nil)
|
21
|
+
Thread.pool(size || DEFAULT_THREAD_POOL_SIZE)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Birdwatcher
|
2
|
+
module Concerns
|
3
|
+
module Core
|
4
|
+
|
5
|
+
# Location of the data directory
|
6
|
+
# @private
|
7
|
+
DATA_DIRECTORY = File.expand_path(
|
8
|
+
File.join(File.dirname(__FILE__), "..", "..", "..", "data")
|
9
|
+
).freeze
|
10
|
+
|
11
|
+
class DataFileNotFoundError < StandardError; end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the current Console instance
|
21
|
+
#
|
22
|
+
# @return [Birdwatcher::Console]
|
23
|
+
def console
|
24
|
+
Birdwatcher::Console.instance
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the currently active workspace model object
|
28
|
+
#
|
29
|
+
# The current workspace is represented by its Sequel data model and can be
|
30
|
+
# used to query for data associated with the workspace.
|
31
|
+
#
|
32
|
+
# Please read the Sequel gem documentation for more information about how
|
33
|
+
# to use the model.
|
34
|
+
#
|
35
|
+
# @return [Birdwatcher::Models::Workspace] instance of the currently active workspace
|
36
|
+
# @see http://sequel.jeremyevans.net/documentation.html
|
37
|
+
def current_workspace
|
38
|
+
Birdwatcher::Console.instance.current_workspace
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the current workspace
|
42
|
+
#
|
43
|
+
# @param workspace [Birdwatcher::Models::Workspace]
|
44
|
+
def current_workspace=(workspace)
|
45
|
+
Birdwatcher::Console.instance.current_workspace = workspace
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get a Twitter API client
|
49
|
+
#
|
50
|
+
# The Twitter API is being queried with the Twitter gem which provides an
|
51
|
+
# easy and intuitive interface to the API and its data objects. Please see
|
52
|
+
# the Twitter gem documentation for information on how to use the Twitter
|
53
|
+
# gem.
|
54
|
+
#
|
55
|
+
# The method will return an instance configured with a random Twitter API
|
56
|
+
# keypair from the +~/.birdwatcherrc+ configuration file.
|
57
|
+
#
|
58
|
+
# @return instance of Twitter::REST::Client
|
59
|
+
# @see https://github.com/sferik/twitter
|
60
|
+
# @see http://www.rubydoc.info/gems/twitter
|
61
|
+
def twitter_client
|
62
|
+
Birdwatcher::Console.instance.twitter_client
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get a Klout API client
|
66
|
+
#
|
67
|
+
# The Klout API provides information about Twitter users such as their
|
68
|
+
# general "social score", topics of interest and social influence graph.
|
69
|
+
#
|
70
|
+
# The method will return an instance configured with a random API key from
|
71
|
+
# the +~/.birdwatcherrc+ configuration file.
|
72
|
+
#
|
73
|
+
# @return [Birdwatcher::KloutClient]
|
74
|
+
# @see https://klout.com/s/developers/v2
|
75
|
+
def klout_client
|
76
|
+
Birdwatcher::Console.instance.klout_client
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get the raw database client instance
|
80
|
+
#
|
81
|
+
# The raw database client object can be used to execute raw SQL queries
|
82
|
+
# against the configured database, however the {current_workspace} method
|
83
|
+
# should be used whenever possible to execute SQL queries through the
|
84
|
+
# current workspace's {Sequel::Model} instance instead. This ensures that
|
85
|
+
# the data returned is isolated to the current workspace.
|
86
|
+
#
|
87
|
+
# @return [Sequel::Database]
|
88
|
+
def database
|
89
|
+
Birdwatcher::Console.instance.database
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get the contents of a data file
|
93
|
+
#
|
94
|
+
# @param name [String] file name to read
|
95
|
+
#
|
96
|
+
# @return contents of file in Birdwatcher's data directory
|
97
|
+
# @raise [Birdwatcher::Concerns::Core::DataFileNotFoundError] if the file doesn't exist
|
98
|
+
def read_data_file(name)
|
99
|
+
path = File.join(DATA_DIRECTORY, name)
|
100
|
+
fail(DataFileNotFoundError, "File #{name} was not found in data directory") unless File.exists?(path)
|
101
|
+
File.read(path)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Birdwatcher
|
2
|
+
module Concerns
|
3
|
+
module Outputting
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
# Output data to the console
|
12
|
+
#
|
13
|
+
# Simply outputs the given data to the console.
|
14
|
+
#
|
15
|
+
# For more convenient and consistant outputting, see the {info}, {task},
|
16
|
+
# {error}, {warn} and {fatal} methods.
|
17
|
+
def output(data)
|
18
|
+
Birdwatcher::Console.instance.output(data)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Output formatted data to the console
|
22
|
+
#
|
23
|
+
# Outputs data with +printf+ formatting.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# output_formatted("%-15s %s\n", title, description)
|
27
|
+
#
|
28
|
+
# @param *args Args to be passed
|
29
|
+
def output_formatted(*args)
|
30
|
+
Birdwatcher::Console.instance.output_formatted(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Output a newline to the console
|
34
|
+
#
|
35
|
+
# Used for consistant spacing in console output
|
36
|
+
def newline
|
37
|
+
Birdwatcher::Console.instance.newline
|
38
|
+
end
|
39
|
+
|
40
|
+
# Output a line to the console
|
41
|
+
#
|
42
|
+
# Used for consistant spacing and separation between console output
|
43
|
+
def line_separator
|
44
|
+
Birdwatcher::Console.instance.line_separator
|
45
|
+
end
|
46
|
+
|
47
|
+
# Output an informational message to the console
|
48
|
+
#
|
49
|
+
# @param message [String] Message to display
|
50
|
+
#
|
51
|
+
# Formats the message as an informational message
|
52
|
+
def info(message)
|
53
|
+
Birdwatcher::Console.instance.info(message)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Output an informational message to the console that reports when a
|
57
|
+
# longer-running task is done.
|
58
|
+
#
|
59
|
+
# @param message [String] Message to display
|
60
|
+
# @param fatal [Boolean] OPTIONAL if an exception is raised, treat it as a fatal error
|
61
|
+
# @param block The code block to yield
|
62
|
+
#
|
63
|
+
# @example performing a long-running task
|
64
|
+
# task("Performing a long, time consuming task...") do
|
65
|
+
# long_running_task
|
66
|
+
# end
|
67
|
+
def task(message, fatal = false, &block)
|
68
|
+
Birdwatcher::Console.instance.task(message, fatal, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Output an error message to the console
|
72
|
+
#
|
73
|
+
# @param message [String] Message to display
|
74
|
+
#
|
75
|
+
# Formats the message as an error message
|
76
|
+
def error(message)
|
77
|
+
Birdwatcher::Console.instance.error(message)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Output a warning message to the console
|
81
|
+
#
|
82
|
+
# @param message [String] Message to display
|
83
|
+
#
|
84
|
+
# Formats the message as a warning message
|
85
|
+
def warn(message)
|
86
|
+
Birdwatcher::Console.instance.warn(message)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Output a fatal message to the console
|
90
|
+
#
|
91
|
+
# @param message [String] Message to display
|
92
|
+
#
|
93
|
+
# Formats the message as a fatal message
|
94
|
+
def fatal(message)
|
95
|
+
Birdwatcher::Console.instance.fatal(message)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Ask the user for confirmation
|
99
|
+
#
|
100
|
+
# @param question [String] Yes/No question to ask the user
|
101
|
+
#
|
102
|
+
# Waits for the user to answer Yes or No to a question. Useful for making
|
103
|
+
# the user confirm destructive actions before executing them.
|
104
|
+
#
|
105
|
+
# @example make user confirm division by zero
|
106
|
+
# if confirm("Do you really want divide by zero?")
|
107
|
+
# 0 / 0
|
108
|
+
# end
|
109
|
+
def confirm(question)
|
110
|
+
HighLine.agree("#{question} (y/n) ")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Birdwatcher
|
2
|
+
module Concerns
|
3
|
+
module Persistence
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
# Save a Twitter status to the database
|
12
|
+
#
|
13
|
+
# @param status [Twitter::Tweet]
|
14
|
+
# @param user [Birdwatcher::Models::User] Author of status
|
15
|
+
#
|
16
|
+
# The status will be linked to the current workspace. All URLs, hashtags
|
17
|
+
# and mentions will automatically be extracted and saved as separate models.
|
18
|
+
#
|
19
|
+
# @return [Birdwatcher::Models::Status]
|
20
|
+
def save_status(status, user)
|
21
|
+
current_workspace = Birdwatcher::Console.instance.current_workspace
|
22
|
+
db_status = current_workspace.add_status(
|
23
|
+
:user_id => user.id,
|
24
|
+
:twitter_id => status.id.to_s,
|
25
|
+
:text => Birdwatcher::Util.strip_control_characters(Birdwatcher::Util.unescape_html(status.text)),
|
26
|
+
:source => Birdwatcher::Util.strip_control_characters(Birdwatcher::Util.strip_html(status.source)),
|
27
|
+
:retweet => status.retweet?,
|
28
|
+
:geo => status.geo?,
|
29
|
+
:favorite_count => status.favorite_count,
|
30
|
+
:retweet_count => status.retweet_count,
|
31
|
+
:possibly_sensitive => status.possibly_sensitive?,
|
32
|
+
:lang => status.lang,
|
33
|
+
:posted_at => status.created_at,
|
34
|
+
)
|
35
|
+
if status.geo? && status.geo.coordinates
|
36
|
+
db_status.longitude = status.geo.coordinates.first
|
37
|
+
db_status.latitude = status.geo.coordinates.last
|
38
|
+
end
|
39
|
+
if status.place?
|
40
|
+
db_status.place_type = status.place.place_type
|
41
|
+
db_status.place_name = Birdwatcher::Util.strip_control_characters(status.place.name)
|
42
|
+
db_status.place_country_code = Birdwatcher::Util.strip_control_characters(status.place.country_code)
|
43
|
+
db_status.place_country = Birdwatcher::Util.strip_control_characters(status.place.country)
|
44
|
+
end
|
45
|
+
db_status.save
|
46
|
+
if status.hashtags?
|
47
|
+
status.hashtags.each do |hashtag|
|
48
|
+
tag = Birdwatcher::Util.strip_control_characters(hashtag.text)
|
49
|
+
db_hashtag = current_workspace.hashtags_dataset.first(:tag => tag) || current_workspace.add_hashtag(:tag => tag)
|
50
|
+
db_status.add_hashtag(db_hashtag)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if status.user_mentions?
|
54
|
+
status.user_mentions.each do |mention|
|
55
|
+
screen_name = Birdwatcher::Util.strip_control_characters(mention.screen_name)
|
56
|
+
name = Birdwatcher::Util.strip_control_characters(mention.name)
|
57
|
+
db_mention = current_workspace.mentions_dataset.first(:twitter_id => mention.id.to_s) || current_workspace.add_mention(:twitter_id => mention.id.to_s, :screen_name => screen_name, :name => name)
|
58
|
+
db_status.add_mention(db_mention)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if status.urls?
|
62
|
+
status.urls.each do |url|
|
63
|
+
expanded_url = Birdwatcher::Util.strip_control_characters(url.expanded_url.to_s)
|
64
|
+
db_url = current_workspace.urls_dataset.first(:url => expanded_url) || current_workspace.add_url(:url => expanded_url)
|
65
|
+
db_status.add_url(db_url)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
db_status
|
69
|
+
end
|
70
|
+
|
71
|
+
# Save a Twitter user to the database
|
72
|
+
#
|
73
|
+
# @param user [Twitter::User]
|
74
|
+
#
|
75
|
+
# The user will be linked to the current workspace
|
76
|
+
#
|
77
|
+
# @return [Birdwatcher::Models::User]
|
78
|
+
def save_user(user)
|
79
|
+
Birdwatcher::Console.instance.current_workspace.add_user(
|
80
|
+
:twitter_id => user.id.to_s,
|
81
|
+
:screen_name => Birdwatcher::Util.strip_control_characters(user.screen_name),
|
82
|
+
:name => Birdwatcher::Util.strip_control_characters(user.name),
|
83
|
+
:location => Birdwatcher::Util.strip_control_characters(user.location),
|
84
|
+
:description => Birdwatcher::Util.strip_control_characters(user.description),
|
85
|
+
:url => (user.website_urls.first ? Birdwatcher::Util.strip_control_characters(user.website_urls.first.expanded_url.to_s) : nil),
|
86
|
+
:profile_image_url => user.profile_image_url_https.to_s,
|
87
|
+
:followers_count => user.followers_count,
|
88
|
+
:friends_count => user.friends_count,
|
89
|
+
:listed_count => user.listed_count,
|
90
|
+
:favorites_count => user.favorites_count,
|
91
|
+
:statuses_count => user.statuses_count,
|
92
|
+
:utc_offset => user.utc_offset,
|
93
|
+
:timezone => user.time_zone,
|
94
|
+
:geo_enabled => user.geo_enabled?,
|
95
|
+
:verified => user.verified?,
|
96
|
+
:lang => user.lang
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Birdwatcher
|
2
|
+
module Concerns
|
3
|
+
module Presentation
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
# Make a user summary output
|
12
|
+
#
|
13
|
+
# @param user [Birdwatcher::Models::User]
|
14
|
+
#
|
15
|
+
# @return [String] Short summary of the user
|
16
|
+
def make_user_summary_output(user)
|
17
|
+
"#{user.name.bold.light_green} (@#{user.screen_name}) #{user.verified ? '*'.bold.light_blue : ''}\n" +
|
18
|
+
"Description:".bold + " #{user.description || 'No description'}\n" +
|
19
|
+
"Location:".bold + " #{user.location || 'Unknown'}\n" +
|
20
|
+
"Followers:".bold + " #{user.followers_count} | " +
|
21
|
+
"Following:".bold + " #{user.friends_count} | " +
|
22
|
+
"Listed:".bold + " #{user.listed_count} | " +
|
23
|
+
"Favorites:".bold + " #{user.favorites_count} | " +
|
24
|
+
"Statuses:".bold + " #{user.statuses_count}\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Output a user summary to the console
|
28
|
+
#
|
29
|
+
# @param user [Birdwatcher::Models::User]
|
30
|
+
def output_user_summary(user)
|
31
|
+
make_user_summary_output(user)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Make user details output
|
35
|
+
#
|
36
|
+
# @param user [Birdwatcher::Models::User]
|
37
|
+
#
|
38
|
+
# @return [String] summary and details about a user
|
39
|
+
def make_user_details_output(user)
|
40
|
+
"#{user.name.bold.light_green} (@#{user.screen_name}) #{user.verified ? '*'.bold.light_blue : ''}\n\n" +
|
41
|
+
|
42
|
+
"Description:".bold + " #{user.description || 'No description'}\n" +
|
43
|
+
" Location:".bold + " #{user.location || 'Unknown'}\n" +
|
44
|
+
" Website:".bold + " #{user.url || 'None'}\n" +
|
45
|
+
" Timezone:".bold + " #{user.timezone}\n" +
|
46
|
+
" Language:".bold + " #{user.lang}\n\n" +
|
47
|
+
|
48
|
+
"Followers:".bold + " #{user.followers_count}\n" +
|
49
|
+
"Following:".bold + " #{user.friends_count}\n" +
|
50
|
+
"Favorites:".bold + " #{user.favorites_count}\n" +
|
51
|
+
" Statuses:".bold + " #{user.statuses_count}\n\n" +
|
52
|
+
|
53
|
+
" Added:".bold + " #{time_ago_in_words(user.created_at)}\n" +
|
54
|
+
"Updated:".bold + (user.updated_at ? " #{time_ago_in_words(user.updated_at)}" : " Never") + "\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Output user details to the console
|
58
|
+
#
|
59
|
+
# @param user [Birdwatcher::Models::User]
|
60
|
+
def output_user_details(user)
|
61
|
+
output make_user_details_output(user)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Make status summary output
|
65
|
+
#
|
66
|
+
# @param status [Birdwatcher::Models::Status]
|
67
|
+
#
|
68
|
+
# @return [String] short summary of status
|
69
|
+
def make_status_summary_output(status)
|
70
|
+
"#{status.user.name.bold.light_green} (@#{status.user.screen_name}) #{status.user.verified ? '*'.bold.light_blue : ''} #{status.posted_at.strftime('%b %e, %H:%M')}\n" +
|
71
|
+
"#{status.text.bold}\n" +
|
72
|
+
"#{'Favorites:'.light_blue} #{status.favorite_count} | " +
|
73
|
+
"#{'Retweets:'.light_blue} #{status.retweet_count}\n"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Output status summary to the console
|
77
|
+
#
|
78
|
+
# @param status [Birdwatcher::Models::Status]
|
79
|
+
def output_status_summary(status)
|
80
|
+
output make_status_summary_output(status)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Make URL summary output
|
84
|
+
#
|
85
|
+
# @param url [Hash]
|
86
|
+
# @return [String] URL summary
|
87
|
+
def make_url_summary_output(url)
|
88
|
+
out = "#{(url[:final_url] || url[:url]).bold}\n" +
|
89
|
+
"#{'Shares:'.bold} #{url[:count]}\n"
|
90
|
+
out += "#{'Title:'.bold} #{url[:title] || 'Unknown'}\n"
|
91
|
+
if url[:http_status]
|
92
|
+
case url[:http_status]
|
93
|
+
when 200..299
|
94
|
+
status = url[:http_status].to_s.bold.light_green
|
95
|
+
when 400..499
|
96
|
+
status = url[:http_status].to_s.bold.light_yellow
|
97
|
+
when 500..599
|
98
|
+
status = url[:http_status].to_s.bold.light_red
|
99
|
+
else
|
100
|
+
status = url[:http_status].to_s.bold
|
101
|
+
end
|
102
|
+
out += "#{'Status Code:'.bold} #{status}\n"
|
103
|
+
else
|
104
|
+
out += "#{'Status Code:'.bold} Unknown\n"
|
105
|
+
end
|
106
|
+
out += "#{'Content Type:'.bold} #{url[:content_type] || 'Unknown'}\n"
|
107
|
+
out
|
108
|
+
end
|
109
|
+
|
110
|
+
# Page potentially long output to the console
|
111
|
+
#
|
112
|
+
# @param text [String] Text to page
|
113
|
+
#
|
114
|
+
# If the text is long, it will be automatically paged with the system's
|
115
|
+
# currently configured pager command (usually `less`).
|
116
|
+
def page_text(text)
|
117
|
+
::TTY::Pager::SystemPager.new.page(text)
|
118
|
+
rescue Errno::EPIPE
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|