murlsh 0.11.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/.htaccess +2 -0
  2. data/README.textile +3 -33
  3. data/Rakefile +12 -5
  4. data/VERSION +1 -1
  5. data/config.ru +10 -4
  6. data/config.yaml +6 -12
  7. data/lib/murlsh/dispatch.rb +0 -6
  8. data/lib/murlsh/img_store.rb +36 -0
  9. data/lib/murlsh/markup.rb +0 -1
  10. data/lib/murlsh/plugin.rb +2 -6
  11. data/lib/murlsh/uri_ask.rb +66 -22
  12. data/lib/murlsh/url.rb +0 -18
  13. data/lib/murlsh/url_body.rb +15 -23
  14. data/lib/murlsh/url_server.rb +5 -5
  15. data/murlsh.gemspec +44 -19
  16. data/plugins/add_post_50_update_feed.rb +17 -3
  17. data/plugins/add_post_50_update_podcast.rb +46 -0
  18. data/plugins/add_post_50_update_rss.rb +18 -2
  19. data/plugins/add_post_60_notify_hubs.rb +3 -1
  20. data/plugins/add_pre_40_convert_mobile.rb +30 -0
  21. data/plugins/add_pre_50_lookup_content_type_title.rb +11 -4
  22. data/plugins/add_pre_60_flickr.rb +38 -0
  23. data/plugins/add_pre_60_github_title.rb +5 -1
  24. data/plugins/add_pre_60_google_code_title.rb +5 -2
  25. data/plugins/add_pre_60_imageshack.rb +31 -0
  26. data/plugins/add_pre_60_imgur.rb +32 -0
  27. data/plugins/add_pre_60_s3_image.rb +34 -0
  28. data/plugins/add_pre_60_twitter.rb +35 -0
  29. data/plugins/add_pre_60_vimeo.rb +35 -0
  30. data/plugins/add_pre_60_youtube.rb +31 -0
  31. data/plugins/html_parse_50_hpricot.rb +2 -0
  32. data/plugins/url_display_add_45_mp3.rb +30 -0
  33. data/plugins/url_display_add_50_hostrec.rb +38 -0
  34. data/plugins/url_display_add_55_content_type.rb +27 -0
  35. data/plugins/url_display_add_60_via.rb +52 -0
  36. data/plugins/url_display_add_65_time.rb +22 -0
  37. data/public/css/jquery.jgrowl.css +0 -3
  38. data/public/css/screen.css +0 -18
  39. data/public/img/thumb/README +0 -0
  40. data/public/js/jquery-1.4.3.min.js +166 -0
  41. data/public/js/js.js +62 -234
  42. data/public/js/twitter-text-1.0.3.js +538 -0
  43. data/spec/img_store_spec.rb +53 -0
  44. data/spec/uri_ask_spec.rb +14 -4
  45. metadata +139 -37
  46. data/lib/murlsh/flickr_server.rb +0 -55
  47. data/lib/murlsh/twitter_server.rb +0 -45
  48. data/lib/murlsh/unwrap_jsonp.rb +0 -15
  49. data/lib/murlsh/xhtml_response.rb +0 -20
  50. data/plugins/hostrec_50_redundant.rb +0 -14
  51. data/plugins/hostrec_60_skip.rb +0 -24
  52. data/plugins/time_50_ago.rb +0 -16
  53. data/plugins/via_50_domain.rb +0 -36
  54. data/public/js/jquery-1.4.2.min.js +0 -154
  55. data/spec/unwrap_json_spec.rb +0 -21
  56. data/spec/xhtml_response_spec.rb +0 -112
data/.htaccess CHANGED
@@ -1,6 +1,8 @@
1
1
  Options -Indexes
2
2
 
3
+ AddOutputFilterByType DEFLATE application/atom+xml
3
4
  AddOutputFilterByType DEFLATE application/javascript
5
+ AddOutputFilterByType DEFLATE application/rss+xml
4
6
  AddOutputFilterByType DEFLATE application/xhtml+xml
5
7
  AddOutputFilterByType DEFLATE application/xml
6
8
  AddOutputFilterByType DEFLATE text/css
data/README.textile CHANGED
@@ -1,7 +1,7 @@
1
1
  Site for sharing and archiving links.
2
2
 
3
3
  * looks up url titles
4
- * adds thumbnails for and jGrowls embedded versions of Flickr, Imageshack, Vimeo and YouTube urls
4
+ * adds thumbnails for and jGrowls embedded versions of Imageshack, Vimeo and YouTube urls
5
5
  * converts Twitter status urls to their full text and adds user thumbnail
6
6
  * generates Atom and RSS feeds
7
7
  * regex search
@@ -12,10 +12,6 @@ Site for sharing and archiving links.
12
12
  * rack interface
13
13
  * Gravatar support
14
14
 
15
- !http://static.mmb.s3.amazonaws.com/murlsh_screenshot.jpg!
16
-
17
- !http://static.mmb.s3.amazonaws.com/murlsh_iphone_screenshot.jpg!
18
-
19
15
  See "http://urls.matthewm.boedicker.org/":http://urls.matthewm.boedicker.org/ for example.
20
16
 
21
17
  h1. Installation
@@ -59,11 +55,9 @@ Plugin hooks
59
55
 
60
56
  |Hook|Description|run() arguments|Returns|
61
57
  |add_pre|called before a new url is saved|url, config hash|undefined|
62
- |add_post|called after a new url is saved|config hash|undefined|
63
- |hostrec|post process the domain that is shown after links|domain, url, title|text to display|
58
+ |add_post|called after a new url is saved|url, config hash|undefined|
64
59
  |html_parse|parse HTML using something like Hpricot or Nokogiri|parseable|parsed HTML, only first plugin is run (cannot be chained)|
65
- |time|convert the time of a post into a string for display|time|time display text|
66
- |via|convert a via url into a string for display|via url|via url display text|
60
+ |url_display_add|called to display additional information after urls|markup builder, url, config hash|undefined|
67
61
 
68
62
  h1. PubSubHubbub
69
63
 
@@ -84,33 +78,9 @@ subscribe_url is what gets put in the feed as link rel="hub"
84
78
 
85
79
  This will make updates to your feed show up in Google Reader instantly.
86
80
 
87
- h1. Thumbnail Locators
88
-
89
- If the url for a thumbnail image can be generated by regex search and replace
90
- on a posted url, a rule can be added to the configuration to automatically
91
- show a thumbnail image for it. The 'thumb_locators' config key is a hash
92
- of Javascript regex to Javascript replacement string (which can include
93
- captured groups). The hash key is wrapped in ^ and $ before compilation so
94
- it must match the entire url. The regex is compiled case-insensitive.
95
-
96
- For example:
97
-
98
- * Show the GitHub octocat Apple touch icon
99
- (http://github.com/apple-touch-icon.png) as a thumbnail for GitHub links
100
- * Show a friend's Gravatar for the thumbnails of links to his blog
101
-
102
- <pre>
103
- <code>
104
- thumb_locators:
105
- (http:\/\/github\.com\/).*: $1apple-touch-icon.png
106
- http:\/\/myfriend\.com\/.*: http://gravatar.com/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</code>
107
- </pre>
108
-
109
81
  h1. Design Goals
110
82
 
111
- * make site fast and very cacheable, caching wrapper around calls to third party JSON APIs
112
83
  * low effort required to add a url, get metadata programatically instead of requiring user to specify
113
- * heavy lifting on the client side
114
84
  * allow customization with config and plugins
115
85
  * full regex search for finding saved urls
116
86
  * simple security (no sessions, no cookies)
data/Rakefile CHANGED
@@ -81,8 +81,10 @@ namespace :db do
81
81
  email TEXT,
82
82
  name TEXT,
83
83
  title TEXT,
84
+ content_length INTEGER,
84
85
  content_type TEXT,
85
- via TEXT);
86
+ via TEXT,
87
+ thumbnail_url TEXT);
86
88
  ")
87
89
  end
88
90
 
@@ -190,8 +192,8 @@ end
190
192
 
191
193
  namespace :validate do
192
194
 
193
- desc 'Validate XHTML.'
194
- task :xhtml do
195
+ desc 'Validate HTML.'
196
+ task :html do
195
197
  check_url = config['root_url']
196
198
  print "validating #{check_url} : "
197
199
  result = validate(check_url)
@@ -340,17 +342,22 @@ begin
340
342
  activerecord 2.3.4
341
343
  bcrypt-ruby 2.1.2
342
344
  builder 2.1.2
345
+ flickraw 0.8.3
346
+ flog 2.5.0
343
347
  hpricot 0.8.1
344
348
  htmlentities 4.2.0
345
349
  json 1.2.3
346
350
  push-notify 0.1.0
347
351
  rack 1.0.0
348
352
  rack-cache 0.5.2
353
+ rack-rewrite 1.0.2
349
354
  rack-throttle 0.3.0
350
355
  sqlite3-ruby 1.2.1
351
- tinyatom 0.1.1
356
+ tinyatom 0.2.0
357
+ twitter 0.9.12
358
+ vimeo 1.2.2
352
359
  }.each_slice(2) { |g,v| gemspec.add_dependency(g, ">= #{v}") }
353
-
360
+ gemspec.add_dependency('rspec', '~> 1.3')
354
361
  end
355
362
  rescue LoadError
356
363
  puts "Jeweler not available. Install it with: gem install jeweler"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.11.0
1
+ 1.0.0
data/config.ru CHANGED
@@ -4,6 +4,7 @@ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
4
4
  yaml
5
5
 
6
6
  rack/cache
7
+ rack/rewrite
7
8
  rack/throttle
8
9
 
9
10
  murlsh
@@ -25,11 +26,16 @@ use Murlsh::EtagAddEncoding
25
26
  use Rack::Deflater
26
27
  use Murlsh::FarFutureExpires, :patterns => %r{\.gen\.(css|js)$}
27
28
 
28
- feed_path = URI.join(config.fetch('root_url'), config.fetch('feed_file')).path
29
- use Murlsh::MustRevalidate, :patterns => %r{^#{Regexp.escape(feed_path)}$}
29
+ feed_url = URI.join(config.fetch('root_url'), config.fetch('feed_file'))
30
+ use Murlsh::MustRevalidate, :patterns => %r{^#{Regexp.escape(feed_url.path)}$}
30
31
 
31
- use Rack::Static, :urls => %w{/css /js /swf}, :root => 'public'
32
- use Rack::Static, :urls => %w{/atom.xml /rss.xml}
32
+ use Rack::Static, :urls => %w{/css /img /js /swf}, :root => 'public'
33
+ use Rack::Static, :urls => %w{/atom.atom /podcast.rss /rss.rss}
34
+
35
+ use Rack::Rewrite do
36
+ r301 '/atom.xml', feed_url.to_s
37
+ r301 '/rss.xml', URI.join(config.fetch('root_url'), 'rss.rss').to_s
38
+ end
33
39
 
34
40
  # use Rack::Lint
35
41
 
data/config.yaml CHANGED
@@ -2,18 +2,19 @@
2
2
  auth_file: murlsh_users
3
3
  cache_entitystore: file:tmp/cache/rack/body
4
4
  cache_metastore: file:tmp/cache/rack/meta
5
- config_js:
6
- - thumb_locators
5
+ config_js: []
6
+
7
7
  css_files:
8
8
  - css/jquery.jgrowl.css
9
9
  - css/screen.css
10
10
  db_file: murlsh.db
11
- feed_file: atom.xml
11
+ feed_file: atom.atom
12
12
  flickr_api_key:
13
13
  gravatar_size: 32
14
14
  js_files:
15
- - js/jquery-1.4.2.min.js
15
+ - js/jquery-1.4.3.min.js
16
16
  - js/jquery.jgrowl_compressed.js
17
+ - js/twitter-text-1.0.3.js
17
18
  - js/js.js
18
19
  meta_tag_description: URLs found interesting by Matthew M. Boedicker
19
20
  meta_tag_verify-v1:
@@ -25,11 +26,4 @@ pubsubhubbub_hubs: []
25
26
 
26
27
  root_url: http://urls.matthewm.boedicker.org/
27
28
  show_names: true
28
- thumb_locators:
29
- (http:\/\/(cgi\.)?ebay\.(com|co\.uk)\/).*: $1apple-touch-icon.png
30
- (http:\/\/blog\.makezine\.com\/).*: $1apple-touch-icon.png
31
- (http:\/\/en\.wikipedia\.org\/).*: $1apple-touch-icon.png
32
- (http:\/\/github\.com\/).*: $1apple-touch-icon.png
33
- (http:\/\/stackoverflow\.com\/).*: $1apple-touch-icon.png
34
- (http:\/\/www\.nytimes\.com\/).*: $1apple-touch-icon.png
35
- (http:\/\/www\.wired\.com\/).*: $1apple-touch-icon.png
29
+ user_agent: murlsh (http://github.com/mmb/murlsh)
@@ -22,8 +22,6 @@ module Murlsh
22
22
 
23
23
  url_server = Murlsh::UrlServer.new(@config, db)
24
24
  config_server = Murlsh::ConfigServer.new(@config)
25
- flickr_server = Murlsh::FlickrServer.new(@config)
26
- twitter_server = Murlsh::TwitterServer.new
27
25
 
28
26
  root_path = URI(@config.fetch('root_url')).path
29
27
 
@@ -33,10 +31,6 @@ module Murlsh
33
31
  [%r{^POST #{root_path}(url)?$}, url_server.method(:post)],
34
32
  [%r{^HEAD #{root_path}config$}, config_server.method(:head)],
35
33
  [%r{^GET #{root_path}config$}, config_server.method(:get)],
36
- [%r{^HEAD #{root_path}flickr$}, flickr_server.method(:head)],
37
- [%r{^GET #{root_path}flickr$}, flickr_server.method(:get)],
38
- [%r{^HEAD #{root_path}twitter/.+$}, twitter_server.method(:head)],
39
- [%r{^GET #{root_path}twitter/.+$}, twitter_server.method(:get)],
40
34
  ]
41
35
  end
42
36
 
@@ -0,0 +1,36 @@
1
+ %w{
2
+ cgi
3
+ open-uri
4
+ }.each { |m| require m }
5
+
6
+ module Murlsh
7
+
8
+ # Fetch images from urls and store them locally.
9
+ class ImgStore
10
+
11
+ def initialize(storage_dir, options={})
12
+ @storage_dir = storage_dir
13
+ @user_agent = options[:user_agent]
14
+ end
15
+
16
+ # Build headers to send with request.
17
+ def headers
18
+ result = {}
19
+ result['User-Agent'] = @user_agent if @user_agent
20
+ result
21
+ end
22
+
23
+ # Fetch an image from a url and store it locally.
24
+ def store(url)
25
+ local_file = CGI.escape(url)
26
+ local_path = File.join(storage_dir, local_file)
27
+ open(url, headers) do |fin|
28
+ open(local_path, 'w') { |fout| fout.write(fin.read) }
29
+ end
30
+ local_file
31
+ end
32
+
33
+ attr_reader :storage_dir
34
+ end
35
+
36
+ end
data/lib/murlsh/markup.rb CHANGED
@@ -53,7 +53,6 @@ module Murlsh
53
53
  attrs = {
54
54
  :href => "#{options[:prefix]}#{href}",
55
55
  :rel => 'stylesheet',
56
- :type => 'text/css',
57
56
  }
58
57
  attrs[:media] = options[:media] if options[:media]
59
58
  link(attrs)
data/lib/murlsh/plugin.rb CHANGED
@@ -7,14 +7,10 @@ module Murlsh
7
7
  # run arguments (url, config hash)
8
8
  # * add_post - called after a new url is saved
9
9
  # run arguments (config hash)
10
- # * hostrec - called to post process the domain that shows after links
11
- # run arguments (domain, url, title)
12
10
  # * html_parse - called to parse HTML using something like Hpricot or Nokogiri
13
11
  # run arguments (parseable)
14
- # * time - called to convert the time of a post into a string for display
15
- # run arguments (time)
16
- # * via - called to convert a via url into a string for display
17
- # run arguments (via url)
12
+ # * url_display_add - called to display additional information after urls
13
+ # run arguments (markup builder, url, config hash)
18
14
  class Plugin
19
15
 
20
16
  # Called when a plugin class inherits from this class (the way plugins
@@ -14,33 +14,19 @@ module Murlsh
14
14
  # URI mixin.
15
15
  module UriAsk
16
16
 
17
- # Get the content type.
17
+ # Get the content length.
18
18
  #
19
19
  # Options:
20
20
  # * :failproof - if true hide all exceptions and return empty string on failure
21
21
  # * :headers - hash of headers to send in request
22
- def content_type(options={})
23
- return @content_type if defined?(@content_type)
24
- options[:headers] = default_headers.merge(options.fetch(:headers, {}))
25
-
26
- content_type = ''
27
- Murlsh::failproof(options) do
28
- # try head first to save bandwidth
29
- http = Net::HTTP.new(host, port)
30
- http.use_ssl = (scheme == 'https')
31
-
32
- resp = http.request_head(path_query, options[:headers])
22
+ def content_length(options={}); header('content-length', options); end
33
23
 
34
- if Net::HTTPSuccess === resp
35
- content_type = resp['content-type']
36
- end
37
-
38
- if not content_type or content_type.empty?
39
- content_type = self.open(options[:headers]) { |f| f.content_type }
40
- end
41
- end
42
- @content_type = content_type
43
- end
24
+ # Get the content type.
25
+ #
26
+ # Options:
27
+ # * :failproof - if true hide all exceptions and return empty string on failure
28
+ # * :headers - hash of headers to send in request
29
+ def content_type(options={}); header('content-type', options); end
44
30
 
45
31
  # Get the HTML title.
46
32
  #
@@ -134,6 +120,64 @@ module Murlsh
134
120
  HTMLEntities.new.decode(Iconv.conv('utf-8', @charset, s))
135
121
  end
136
122
 
123
+ # Get the value of a response header.
124
+ #
125
+ # Options:
126
+ # * :failproof - if true hide all exceptions and return empty string on failure
127
+ # * :headers - hash of headers to send in request
128
+ def header(header_name, options={})
129
+ result = [*head_headers(options)[header_name]][0]
130
+ result = get_headers(options)[header_name] if !result or result.empty?
131
+ result || ''
132
+ end
133
+
134
+ # Get and cache response headers returned by HTTP HEAD for this URI.
135
+ #
136
+ # Return hash values are lists.
137
+ #
138
+ # Options:
139
+ # * :failproof - if true hide all exceptions and return empty hash on failure
140
+ # * :headers - hash of headers to send in request
141
+ def head_headers(options={})
142
+ return @head_headers if defined?(@head_headers)
143
+
144
+ request_headers = default_headers.merge(options.fetch(:headers, {}))
145
+
146
+ response_headers = {}
147
+ Murlsh::failproof(options) do
148
+ http = Net::HTTP.new(host, port)
149
+ http.use_ssl = (scheme == 'https')
150
+
151
+ resp = http.request_head(path_query, request_headers)
152
+
153
+ if Net::HTTPSuccess === resp
154
+ response_headers = resp.to_hash
155
+ end
156
+
157
+ end
158
+ @head_headers = response_headers
159
+ end
160
+
161
+ # Get and cache response headers returned by HTTP GET for this URI.
162
+ #
163
+ # Return hash values are single strings.
164
+ #
165
+ # Options:
166
+ # * :failproof - if true hide all exceptions and return empty hash on failure
167
+ # * :headers - hash of headers to send in request
168
+ def get_headers(options={})
169
+ return @get_headers if defined?(@get_headers)
170
+
171
+ request_headers = default_headers.merge(options.fetch(:headers, {}))
172
+
173
+ response_headers = {}
174
+ # use open-uri instead of Net::HTTP because it handles redirects
175
+ Murlsh::failproof(options) do
176
+ response_headers = self.open(request_headers) { |f| f.meta }
177
+ end
178
+ @get_headers = response_headers
179
+ end
180
+
137
181
  end
138
182
 
139
183
  end
data/lib/murlsh/url.rb CHANGED
@@ -31,24 +31,6 @@ module Murlsh
31
31
  email and name and email == other.email and name == other.name
32
32
  end
33
33
 
34
- # Return text showing what domain a link goes to.
35
- def hostrec
36
- domain = Murlsh::failproof { URI(url).domain }
37
-
38
- domain = Murlsh::Plugin.hooks('hostrec').inject(domain) {
39
- |result,plugin| plugin.run(result, url, title) }
40
-
41
- yield domain if domain
42
- end
43
-
44
- # Yield the url that the url came from.
45
- def viarec; Murlsh::failproof { yield URI(via) } if via; end
46
-
47
- # Return true if this url is an image.
48
- def is_image?
49
- %w{image/gif image/jpeg image/png}.include?(content_type)
50
- end
51
-
52
34
  end
53
35
 
54
36
  end
@@ -8,9 +8,10 @@ module Murlsh
8
8
  class UrlBody < Builder::XmlMarkup
9
9
  include Murlsh::Markup
10
10
 
11
- def initialize(config, db, req)
12
- @config, @db, @req, @q = config, db, req, req.params['q']
13
- super(:indent => @config['xhtml_indent'] || 0)
11
+ def initialize(config, db, req, content_type='text/html')
12
+ @config, @db, @req, @q, @content_type =
13
+ config, db, req, req.params['q'], content_type
14
+ super(:indent => @config['html_indent'] || 0)
14
15
  end
15
16
 
16
17
  # Fetch urls base on query string parameters.
@@ -33,14 +34,9 @@ module Murlsh
33
34
 
34
35
  # Url list page body builder.
35
36
  def each
36
- instruct! :xml
37
- declare! :DOCTYPE, :html, :PUBLIC, '-//W3C//DTD XHTML 1.1//EN',
38
- 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
39
-
40
- yield html(:xmlns => 'http://www.w3.org/1999/xhtml',
41
- :'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
42
- :'xsi:schemaLocation' => 'http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd',
43
- :'xml:lang' => 'en') {
37
+ declare! :DOCTYPE, :html
38
+
39
+ yield html(:lang => 'en') {
44
40
  headd
45
41
  body {
46
42
  ul(:id => 'urls') {
@@ -59,22 +55,17 @@ module Murlsh
59
55
  @config.fetch('show_names', false) and mu.name
60
56
  end
61
57
 
58
+ if mu.thumbnail_url
59
+ murlsh_img(:src => mu.thumbnail_url,
60
+ :text => mu.title_stripped, :class => 'thumb')
61
+ end
62
+
62
63
  a(mu.title_stripped, :href => mu.url, :class => 'm')
63
64
 
64
- mu.hostrec do |hostrec|
65
- self.span(" [#{hostrec}]", :class => 'host')
66
- end
67
- mu.viarec do |via|
68
- display_via = Murlsh::Plugin.hooks('via').inject(
69
- via) { |result,plugin| plugin.run(result) }
70
- span(:class => 'via') {
71
- text!(' via '); a(display_via, :href => via)
72
- }
65
+ Murlsh::Plugin.hooks('url_display_add') do |p|
66
+ p.run(self, mu, @config)
73
67
  end
74
68
 
75
- display_time = Murlsh::Plugin.hooks('time').inject(
76
- mu.time) { |result,plugin| plugin.run(result) }
77
- span(", #{display_time}", :class => 'date') if display_time
78
69
  last = mu
79
70
  }
80
71
  end
@@ -94,6 +85,7 @@ module Murlsh
94
85
  def headd
95
86
  head {
96
87
  titlee
88
+ meta :'http-equiv' => 'Content-Type', :content => @content_type
97
89
  metas(@config.select { |k,v| k =~ /^meta_tag_/ and v }.
98
90
  map { |k,v| [k.sub('meta_tag_', ''), v] })
99
91
  css(@config['css_compressed'] || @config['css_files'])
@@ -20,16 +20,16 @@ module Murlsh
20
20
  # Respond to a GET request. Return a page of urls based on the query
21
21
  # string parameters.
22
22
  def get(req)
23
- resp = Murlsh::XhtmlResponse.new
23
+ last_db_update = File::Stat.new(@config['db_file']).mtime
24
24
 
25
- resp.set_content_type(req.env['HTTP_ACCEPT'], req.env['HTTP_USER_AGENT'])
25
+ resp = Rack::Response.new
26
26
 
27
- last_db_update = File::Stat.new(@config['db_file']).mtime
28
27
  resp['Cache-Control'] = 'must-revalidate, max-age=0'
28
+ resp['Content-Type'] = 'text/html; charset=utf-8'
29
29
  resp['ETag'] = "W/\"#{last_db_update.to_i}#{req.params.sort}\""
30
30
  resp['Last-Modified'] = last_db_update.httpdate
31
31
 
32
- resp.body = Murlsh::UrlBody.new(@config, @db, req)
32
+ resp.body = Murlsh::UrlBody.new(@config, @db, req, resp['Content-Type'])
33
33
 
34
34
  resp
35
35
  end
@@ -55,7 +55,7 @@ module Murlsh
55
55
  raise ActiveRecord::RecordInvalid.new(mu) unless mu.valid?
56
56
  Murlsh::Plugin.hooks('add_pre') { |p| p.run(mu, @config) }
57
57
  mu.save!
58
- Murlsh::Plugin.hooks('add_post') { |p| p.run(@config) }
58
+ Murlsh::Plugin.hooks('add_post') { |p| p.run(mu, @config) }
59
59
  response_body, response_code = [mu], 200
60
60
  rescue ActiveRecord::RecordInvalid => error
61
61
  response_body = {