dash-bees 0.21 → 0.22

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,16 @@
1
+ 2010-09-23 v0.22 Icons, origins and bug fixes.
2
+
3
+ API change: using special field names (DashFu::Bee::Fields).
4
+
5
+ Added icon URL, and each source gets to report text/URL to the origin (e.g.
6
+ service, search page). These will be shown separate from metadata.
7
+
8
+ Fixed tweet count for Backtweet source.
9
+
10
+ Fixed handling of feeds that lack a title.
11
+
12
+ GitHub/issues now accept repository URL.
13
+
1
14
  2010-09-20 v0.21 Improvements and bug fixes for feed source
2
15
 
3
16
  Truncates long contents.
data/Gemfile CHANGED
@@ -9,7 +9,6 @@ end
9
9
  group :test do
10
10
  gem "awesome_print"
11
11
  gem "builder"
12
- gem "shoulda"
13
12
  gem "timecop"
14
13
  gem "vcr"
15
14
  gem "webmock"
@@ -1,5 +1,5 @@
1
1
  $: << File.dirname(__FILE__) + "/lib"
2
- require "dash-fu/bee"
2
+ require "dash-fu/bee/version"
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = "dash-bees"
@@ -3,14 +3,18 @@ require "net/http"
3
3
  require "uri"
4
4
  require "open-uri"
5
5
  require "rack"
6
+ require "nokogiri"
7
+ require "sanitize"
8
+ require "dash-fu/bee/version"
9
+ require "dash-fu/bee/http_session"
10
+ require "dash-fu/bee/fields"
6
11
 
7
12
  # See http://dash-fu.com
8
13
  module DashFu
9
14
 
10
15
  # The README covers it all.
11
16
  module Bee
12
-
13
- VERSION = "0.21"
17
+ include DashFu::Bee::Fields
14
18
 
15
19
  class << self
16
20
  attr_accessor :logger
@@ -132,47 +136,7 @@ module DashFu
132
136
  http = Net::HTTP.new(host, port)
133
137
  http.use_ssl = true if port == 443
134
138
  http.start do |http|
135
- yield Session.new(http)
136
- end
137
- end
138
-
139
- # HTTP Session.
140
- class Session
141
- def initialize(http) #:nodoc:
142
- @http = http
143
- end
144
-
145
- # Make a GET request and yield response to the block. Response consists of
146
- # three arguments: status code, response body and response headers. The
147
- # block may be called asynchronoulsy.
148
- def get(path, headers = {}, &block)
149
- response = @http.request(get_request(path, headers || {}))
150
- yield response.code.to_i, response.body, {}
151
- end
152
-
153
- # Make a GET request and yield response to the block. If the response
154
- # status is 200 the second argument is the response JSON object. The block
155
- # may be called asynchronously.
156
- def get_json(path, headers = {}, &block)
157
- response = @http.request(get_request(path, headers || {}))
158
- if Net::HTTPOK === response
159
- json = JSON.parse(response.body) rescue nil
160
- if json
161
- yield response.code.to_i, json, {}
162
- else
163
- yield 500, "Not a JSON document", {}
164
- end
165
- else
166
- yield response.code.to_i, response.message, {}
167
- end
168
- end
169
-
170
- protected
171
-
172
- def get_request(path, headers)
173
- request = Net::HTTP::Get.new(path)
174
- request.basic_auth headers[:username], headers[:password] if headers[:username] && headers[:password]
175
- request
139
+ yield DashFu::HTTPSession.new(http)
176
140
  end
177
141
  end
178
142
 
@@ -0,0 +1,17 @@
1
+ module DashFu::Bee
2
+ module Fields
3
+
4
+ # Suggested name for the source.
5
+ SOURCE_NAME = "_name"
6
+ # Information about the origin of the data, using two fields, text and URL.
7
+ ORIGIN = "_origin"
8
+ # URL for an icon for this feed. Icon should have 1:1 aspect ration,
9
+ # 48px/48px is the ideal isize.
10
+ ICON = "_icon"
11
+
12
+ # If this source has a metric, enumerates the columns.
13
+ METRIC_COLUMNS = "_metric_columns"
14
+ # True if there's a metric and it collects totals
15
+ METRIC_TOTALS = "_metric_totals"
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ require "json"
2
+ require "net/http"
3
+
4
+ module DashFu
5
+ # HTTP Session.
6
+ class HTTPSession
7
+ def initialize(http) #:nodoc:
8
+ @http = http
9
+ end
10
+
11
+ # Make a GET request and yield response to the block. Response consists of
12
+ # three arguments: status code, response body and response headers. The
13
+ # block may be called asynchronoulsy.
14
+ def get(path, headers = {}, &block)
15
+ response = @http.request(get_request(path, headers || {}))
16
+ yield response.code.to_i, response.body, {}
17
+ end
18
+
19
+ # Make a GET request and yield response to the block. If the response
20
+ # status is 200 the second argument is the response JSON object. The block
21
+ # may be called asynchronously.
22
+ def get_json(path, headers = {}, &block)
23
+ response = @http.request(get_request(path, headers || {}))
24
+ if Net::HTTPOK === response
25
+ json = JSON.parse(response.body) rescue nil
26
+ if json
27
+ yield response.code.to_i, json, {}
28
+ else
29
+ yield 500, "Not a JSON document", {}
30
+ end
31
+ else
32
+ yield response.code.to_i, response.message, {}
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def get_request(path, headers)
39
+ request = Net::HTTP::Get.new(path)
40
+ request.basic_auth headers[:username], headers[:password] if headers[:username] && headers[:password]
41
+ request
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ module DashFu
2
+ module Bee
3
+ VERSION = "0.22"
4
+ end
5
+ end
@@ -4,15 +4,21 @@ module DashFu::Bee
4
4
  include DashFu::Bee
5
5
 
6
6
  def setup(source, params)
7
- url = params["url"].strip.downcase.sub(/^http(s?):\/\//, "")
8
- source["source.name"] = "Tweets for #{url}"
9
- source["metric.columns"] = [{ id: "tweets", label: "Tweets" }]
10
- source["metric.totals"] = true
11
- source["url"] = url
7
+ uri = URI.parse(params["url"]).normalize
8
+ uri = URI.parse("http://#{uri}").normalize if uri.scheme.nil?
9
+ source["url"] = uri.to_s
10
+ short = source["url"].gsub(/^https?:\/\//, "")
11
+ short.gsub!(/\/$/, "") if uri.path == "/"
12
+
13
+ source[SOURCE_NAME] = "Tweets for #{short}"
14
+ source[ORIGIN] = { text: "Backtweets for #{short}", url: "http://backtweets.com/search?q=#{URI.escape source["url"]}" }
15
+ source[METRIC_COLUMNS] = [{ id: "tweets", label: "Tweets" }]
16
+ source[METRIC_TOTALS] = true
12
17
  end
13
18
 
14
19
  def validate(source)
15
- raise "Missing URL" if source["url"].blank?
20
+ uri = URI.parse(source["url"])
21
+ raise "This doesn't look like a valid page name, please check your URL" unless uri.absolute? && uri.scheme == "http" && !uri.host.blank?
16
22
  end
17
23
 
18
24
  def update(source, callback)
@@ -20,6 +26,9 @@ module DashFu::Bee
20
26
  http.get_json "/search.json?key=#{api_key}&q=#{Rack::Utils.escape source["url"]}" do |status, json|
21
27
  if status == 200 && tweets = json["tweets"]
22
28
  last_tweet_id = source["last_tweet_id"]
29
+ # Since we don't get much in terms of history, we only look at this
30
+ # count once; afterwards, we increment for each tweet we find.
31
+ callback.set! tweets: json["totalresults"].to_i unless last_tweet_id
23
32
  new_ones = tweets.take_while { |tweet| tweet["tweet_id"] != last_tweet_id }.reverse
24
33
  new_ones.each do |tweet|
25
34
  screen_name, id = tweet["tweet_from_user"], tweet["tweet_id"]
@@ -30,11 +39,11 @@ module DashFu::Bee
30
39
  HTML
31
40
  person = { fullname: screen_name, identities: %W{twitter.com:#{screen_name}},
32
41
  photo_url: "http://img.tweetimag.es/i/#{screen_name}_n" }
33
- callback.activity! uid: id, url: url, html: html, tags: %w{twitter mention},
42
+ callback.activity! uid: id, url: url, html: html, tags: %w{twitter mention}, public: true,
34
43
  timestamp: Time.parse(tweet["tweet_created_at"]).utc, person: person
35
44
  source["last_tweet_id"] = tweet["tweet_id"]
36
45
  end
37
- callback.set! tweets: json["totalresults"].to_i
46
+ callback.inc! tweets: new_ones.count if last_tweet_id
38
47
  else
39
48
  callback.error! "Last request didn't go as expected, trying again later"
40
49
  end
@@ -42,8 +51,5 @@ module DashFu::Bee
42
51
  end
43
52
  end
44
53
 
45
- def meta(source)
46
- [ { text: "Search yourself", url: "http://backtweets.com/search?q=#{URI.escape source["url"]}" } ]
47
- end
48
54
  end
49
55
  end
@@ -1,37 +1,40 @@
1
- require "sanitize"
2
- require "nokogiri"
3
-
4
1
  module DashFu::Bee
5
2
  # Track Web feed (Atom or RSS).
6
3
  class Feed
7
4
  include DashFu::Bee
8
5
 
9
6
  def setup(source, params)
10
- source["url"] = params["url"].strip
11
- source["source.name"] = "Web feed"
12
- end
7
+ unfeed = params["url"].strip.gsub(/^feed:\/\//i, "http://").gsub(/^feed:/i, "")
8
+ uri = URI.parse(unfeed).normalize
9
+ uri = URI.parse("http://#{uri}").normalize if uri.scheme.nil?
10
+ source["url"] = uri.to_s
11
+ return unless valid_uri?(uri)
13
12
 
14
- def validate(source)
15
- unfeed = source["url"].gsub(/^feed:\/\//, "http://").gsub(/^feed:/, "")
16
- uri = URI.parse(unfeed) rescue nil
17
- raise "Not a valid URL" unless uri && uri.absolute?
18
- raise "Only HTTP/S URLs supported" unless uri.scheme == "http" || uri.scheme == "https"
19
13
  begin
20
14
  uri.open read_timeout: 3, redirect: true do |io|
21
15
  code = io.status.first
22
16
  raise "Cannot read this feed, got status code #{code}" unless code == "200"
23
17
  feed = (Nokogiri::XML(io.read)>"feed").first
24
- source["source.name"] = source["title"] = get_text(feed>"title").strip
18
+ title = get_text(feed>"title").strip
19
+ title = uri.to_s if title.blank?
20
+
21
+ source[SOURCE_NAME] = title
25
22
  alt = (feed>"link[rel=alternate]").first
26
- source["permalink"] = alt["href"] if alt
27
- source["logo"] = (feed>"logo").text
28
- source["url"] = uri.to_s
23
+ permalink = alt ? alt["href"].strip : uri.to_s
24
+ source[ORIGIN] = { text: title, url: permalink }
25
+ source[ICON] = (feed>"icon").text
29
26
  end
30
27
  rescue
28
+ puts $!
31
29
  raise "Cannot read this feed: is it down for you or just for us?"
32
30
  end
33
31
  end
34
32
 
33
+ def validate(source)
34
+ uri = URI.parse(source["url"])
35
+ raise "This doesn't look like a valid feed, please check your URL" unless valid_uri?(uri)
36
+ end
37
+
35
38
  def update(source, callback)
36
39
  uri = URI.parse(source["url"])
37
40
  session uri.host, uri.port do |http|
@@ -77,9 +80,7 @@ module DashFu::Bee
77
80
  end
78
81
 
79
82
  def meta(source)
80
- meta = []
81
- meta << { title: "Source", text: source["title"], url: source["permalink"] }
82
- meta
83
+ []
83
84
  end
84
85
 
85
86
  def get_text(elements)
@@ -102,5 +103,9 @@ module DashFu::Bee
102
103
  Sanitize.clean(truncate_html(html.to_s.strip, length), Sanitize::Config::BASIC.merge(output: :html)).strip
103
104
  end
104
105
 
106
+ def valid_uri?(uri)
107
+ uri.absolute? && (uri.scheme == "http" || uri.scheme == "https") && !uri.host.blank?
108
+ end
109
+
105
110
  end
106
111
  end
@@ -1,7 +1,7 @@
1
1
  en:
2
2
  description: "Gather activities from an Atom feed"
3
3
  inputs: |-
4
- <label>Feed URL <input type="text" name="source[url]" size="50"></label>
4
+ <label>Feed URL <input type="text" name="source[url]" size="70"></label>
5
5
  notes: |-
6
6
  <ul>
7
7
  </ul>
@@ -3,16 +3,22 @@ module DashFu::Bee
3
3
  class Github
4
4
  include DashFu::Bee
5
5
 
6
+ ICON_URL = "http://dash-fu.com/images/sources/github.png"
7
+
6
8
  def setup(source, params)
7
9
  repo = params["repo"].strip
8
- source["source.name"] = "GitHub: #{repo}"
9
- source["metric.columns"] = [{ id: "commits", label: "Commits" }, { id: "watchers", label: "Watchers" }, { id: "forks", label: "Forks" }]
10
- source["metric.totals"] = true
10
+ repo = $1 if repo =~ /^http:\/\/github\.com\/([\w-]+\/[\w-]+)/i
11
11
  source["repo"] = repo
12
12
  branch = params["branch"].strip
13
13
  source["branch"] = branch.blank? ? "master" : branch
14
14
  source["username"] = params["username"]
15
15
  source["api_token"] = params["api_token"]
16
+
17
+ source[SOURCE_NAME] = "GitHub: #{repo}"
18
+ source[METRIC_COLUMNS] = [{ id: "commits", label: "Commits" }, { id: "watchers", label: "Watchers" }, { id: "forks", label: "Forks" }]
19
+ source[METRIC_TOTALS] = true
20
+ source[ORIGIN] = { text: repo, url: "http://github.com/#{repo}" }
21
+ source[ICON] = ICON_URL
16
22
  end
17
23
 
18
24
  def validate(source)
@@ -22,7 +28,7 @@ module DashFu::Bee
22
28
 
23
29
  def update(source, callback)
24
30
  session "github.com", 443 do |http|
25
- auth = { username: "#{source["username"]}/token", password: source["api_token"] }
31
+ auth = { username: "#{source["username"]}/token", password: source["api_token"] } unless source["username"].blank?
26
32
  http.get_json "/api/v2/json/repos/show/#{source["repo"]}", auth do |status, json|
27
33
  case status
28
34
  when 200
@@ -63,7 +69,7 @@ module DashFu::Bee
63
69
  person = { fullname: committer["name"], identities: Array.wrap(identity), email: committer["email"] }
64
70
  messages = commits.map { |commit| %{<blockquote><a href="#{commit["url"]}">#{commit["id"][0,7]}</a> #{h commit["message"].strip.split(/[\n\r]/).first[0,50]}</blockquote>} }
65
71
  html = %{pushed to #{h source["branch"]} at <a href="http://github.com/#{source["repo"]}">#{h source["repo"]}</a>:\n#{messages.join("\n")}}
66
- callback.activity! uid: first["id"], html: html, url: first["url"], tags: %w{push},
72
+ callback.activity! uid: first["id"], html: html, url: first["url"], tags: %w{push}, public: auth.nil?,
67
73
  timestamp: Time.parse(first["committed_date"]).utc, person: person
68
74
  end
69
75
  callback.inc! commits: new_ones.count
@@ -82,8 +88,7 @@ module DashFu::Bee
82
88
  end
83
89
 
84
90
  def meta(source)
85
- meta = [ { title: "Repository", text: source["repo"], url: source["url"] },
86
- { title: "Branch", text: source["branch"] },
91
+ meta = [ { title: "Branch", text: source["branch"] },
87
92
  { text: source["description"] },
88
93
  { title: "Home page", url: source["homepage"] } ]
89
94
  if last_commit = source["last_commit"]
@@ -3,14 +3,20 @@ module DashFu::Bee
3
3
  class GithubIssues
4
4
  include DashFu::Bee
5
5
 
6
+ ICON_URL = "http://dash-fu.com/images/sources/github.png"
7
+
6
8
  def setup(source, params)
7
9
  repo = params["repo"].to_s.strip
8
- source["source.name"] = "Github Issues for #{repo}"
9
- source["metric.columns"] = [{ id: "open", label: "Open issues" }, { id: "closed", label: "Closed issues" }]
10
- source["metric.totals"] = true
10
+ repo = $1 if repo =~ /^http:\/\/github\.com\/([\w-]+\/[\w-]+)/i
11
11
  source["repo"] = repo
12
12
  source["username"] = params["username"]
13
13
  source["api_token"] = params["api_token"]
14
+
15
+ source[SOURCE_NAME] = "GitHub issues for #{repo}"
16
+ source[METRIC_COLUMNS] = [{ id: "open", label: "Open issues" }, { id: "closed", label: "Closed issues" }]
17
+ source[METRIC_TOTALS] = true
18
+ source[ORIGIN] = { text: repo, url: "http://github.com/#{repo}" }
19
+ source[ICON] = ICON_URL
14
20
  end
15
21
 
16
22
  def validate(source)
@@ -19,9 +25,9 @@ module DashFu::Bee
19
25
  end
20
26
 
21
27
  def update(source, callback)
22
- github = { fullname: "GitHub", identities: %w{site:github.com}, photo_url: "http://github.com/images/error/octocat_happy.gif" }
28
+ github = { fullname: "GitHub", identities: %w{site:github.com}, photo_url: ICON_URL }
23
29
  session "github.com", 443 do |http|
24
- auth = { username: "#{source["username"]}/token", password: source["api_token"] }
30
+ auth = { username: "#{source["username"]}/token", password: source["api_token"] } unless source["username"].blank?
25
31
  http.get_json "/api/v2/json/issues/list/#{source["repo"]}/open" do |status, json|
26
32
  case status
27
33
  when 200
@@ -35,7 +41,7 @@ module DashFu::Bee
35
41
  opened <a href="#{url}">issue #{issue["number"]}</a> on #{source["repo"]}:
36
42
  <blockquote>#{h issue["title"]}</blockquote>
37
43
  HTML
38
- callback.activity! uid: sha, url: url, html: html, tags: %w{issue opened},
44
+ callback.activity! uid: sha, url: url, html: html, tags: %w{issue opened}, public: !auth,
39
45
  person: github, timestamp: Time.parse(issue["created_at"]).utc
40
46
  open_ids << issue["number"]
41
47
  end
@@ -63,7 +69,7 @@ opened <a href="#{url}">issue #{issue["number"]}</a> on #{source["repo"]}:
63
69
  closed <a href="#{url}">issue #{issue["number"]}</a> on #{source["repo"]}:
64
70
  <blockquote>#{h issue["title"]}</blockquote>
65
71
  HTML
66
- callback.activity! uid: sha, url: url, html: html, tags: %w{issue closed},
72
+ callback.activity! uid: sha, url: url, html: html, tags: %w{issue closed}, public: !auth,
67
73
  person: github, timestamp: Time.parse(issue["closed_at"]).utc
68
74
  closed_ids << issue["number"]
69
75
  end
@@ -74,9 +80,5 @@ closed <a href="#{url}">issue #{issue["number"]}</a> on #{source["repo"]}:
74
80
  end
75
81
  end
76
82
 
77
- def meta(source)
78
- [ { title: "On GitHub", url: "http://github.com/#{source["repo"]}/issues" } ]
79
- end
80
-
81
83
  end
82
84
  end
@@ -3,12 +3,17 @@ module DashFu::Bee
3
3
  class RubyGems
4
4
  include DashFu::Bee
5
5
 
6
+ ICON_URL = "http://dash-fu.com/images/sources/ruby.png"
7
+
6
8
  def setup(source, params)
7
9
  gem_name = params["gem_name"].to_s.strip
8
- source["source.name"] = "RubyGems: #{gem_name}"
9
- source["metric.columns"] = [{ id: "downloads", label: "Downloads" }]
10
- source["metric.totals"] = true
11
10
  source["gem_name"] = gem_name
11
+
12
+ source[SOURCE_NAME] = "RubyGems: #{gem_name}"
13
+ source[METRIC_COLUMNS] = [{ id: "downloads", label: "Downloads" }]
14
+ source[METRIC_TOTALS] = true
15
+ source[ORIGIN] = { text: "RubyGems: #{gem_name}", url: "http://rubygems.org/gems/#{gem_name}" }
16
+ source[ICON] = ICON_URL
12
17
  end
13
18
 
14
19
  def validate(source)
@@ -25,8 +30,8 @@ module DashFu::Bee
25
30
  current, latest = Gem::Version.new(source["version"]), Gem::Version.new(json["version"])
26
31
  if latest > current
27
32
  html = "released <a href=\"http://rubygems.org/gems/#{gem_name}/versions/#{latest}\">#{gem_name} version #{latest}</a>."
28
- person = { fullname: "RubyGems", identities: "url:http://rubygems.org/", photo_url: "http://dash-fu.com/images/sources/ruby.png" }
29
- callback.activity! uid: "#{gem_name}-#{latest}", url: "http://rubygems.org/gems/#{gem_name}",
33
+ person = { fullname: "RubyGems", identities: "url:http://rubygems.org/", photo_url: ICON_URL }
34
+ callback.activity! uid: "#{gem_name}-#{latest}", url: "http://rubygems.org/gems/#{gem_name}", public: true,
30
35
  html: html, tags: %w{release}, person: person
31
36
  end
32
37
  end
@@ -46,8 +51,7 @@ module DashFu::Bee
46
51
  [ { title: "Project", text: source["gem_name"], url: source["homepage_uri"] || source["project_uri"] },
47
52
  { text: source["info"] },
48
53
  { title: "Version", text: source["version"] },
49
- { title: "Authors", text: source["authors"] },
50
- { title: "Source", text: "RubyGems", url: source["project_uri"] } ]
54
+ { title: "Authors", text: source["authors"] } ]
51
55
  end
52
56
 
53
57
  end