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,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
|