murlsh 1.4.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.htaccess +0 -3
- data/Gemfile +41 -0
- data/README.textile +68 -6
- data/Rakefile +65 -152
- data/config.ru +13 -2
- data/config.yaml +9 -4
- data/db/migrate/20110213023123_init.rb +20 -0
- data/lib/murlsh/atom_body.rb +78 -0
- data/lib/murlsh/atom_server.rb +38 -0
- data/lib/murlsh/auth.rb +12 -0
- data/lib/murlsh/build_query.rb +1 -1
- data/lib/murlsh/cat_files.rb +17 -0
- data/lib/murlsh/delicious_parse.rb +1 -0
- data/lib/murlsh/dispatch.rb +25 -11
- data/lib/murlsh/etag_add_encoding.rb +1 -1
- data/lib/murlsh/feed_body.rb +36 -0
- data/lib/murlsh/img_store.rb +27 -28
- data/lib/murlsh/install.rb +1 -0
- data/lib/murlsh/json_body.rb +5 -2
- data/lib/murlsh/json_server.rb +9 -13
- data/lib/murlsh/m3u_body.rb +28 -0
- data/lib/murlsh/m3u_server.rb +50 -0
- data/lib/murlsh/markup.rb +1 -1
- data/lib/murlsh/plugin.rb +5 -0
- data/lib/murlsh/podcast_server.rb +44 -0
- data/lib/murlsh/pop_server.rb +78 -0
- data/lib/murlsh/random_server.rb +41 -0
- data/lib/murlsh/rss_body.rb +46 -0
- data/lib/murlsh/rss_server.rb +38 -0
- data/lib/murlsh/search_conditions.rb +2 -2
- data/lib/murlsh/uri_ask.rb +2 -2
- data/lib/murlsh/url_body.rb +21 -6
- data/lib/murlsh/url_result_set.rb +2 -2
- data/lib/murlsh/url_server.rb +19 -16
- data/lib/murlsh/write_ordered_hash.rb +17 -0
- data/lib/murlsh.rb +13 -2
- data/murlsh.gemspec +41 -194
- data/plugins/add_post_60_notify_hubs.rb +3 -2
- data/plugins/add_pre_30_unajax_twitter.rb +1 -1
- data/plugins/add_pre_40_thumbnail_shortcuts.rb +23 -0
- data/plugins/add_pre_45_supplied_thumbnail.rb +4 -9
- data/plugins/add_pre_50_media_thumbnail.rb +4 -9
- data/plugins/add_pre_50_open_graph_image.rb +4 -8
- data/plugins/add_pre_60_github_title.rb +1 -1
- data/plugins/add_pre_65_html_thumb.rb +3 -8
- data/plugins/add_pre_65_img_thumb.rb +4 -9
- data/plugins/avatar_50_gravatar.rb +2 -1
- data/plugins/store_asset_40_s3.rb +40 -0
- data/plugins/store_asset_50_local.rb +22 -0
- data/public/js/js.js +0 -7
- data/spec/auth_spec.rb +7 -0
- data/spec/cat_files_spec.rb +49 -0
- data/spec/img_store_spec.rb +24 -8
- metadata +119 -76
- data/VERSION +0 -1
- data/lib/murlsh/build_md5.rb +0 -12
- data/lib/murlsh/head_from_get.rb +0 -15
- data/plugins/add_post_50_update_feed.rb +0 -84
- data/plugins/add_post_50_update_m3u.rb +0 -35
- data/plugins/add_post_50_update_podcast.rb +0 -44
- data/plugins/add_post_50_update_rss.rb +0 -51
- data/plugins/add_pre_60_s3_image.rb +0 -35
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
require 'murlsh'
|
6
|
+
|
7
|
+
module Murlsh
|
8
|
+
|
9
|
+
# Serve Atom feed.
|
10
|
+
class AtomServer
|
11
|
+
|
12
|
+
def initialize(config); @config = config; end
|
13
|
+
|
14
|
+
# Respond to a GET request for Atom feed.
|
15
|
+
def get(req)
|
16
|
+
conditions = Murlsh::SearchConditions.new(req['q']).conditions
|
17
|
+
page = 1
|
18
|
+
per_page = config.fetch('num_posts_feed', 25)
|
19
|
+
|
20
|
+
result_set = Murlsh::UrlResultSet.new(conditions, page, per_page)
|
21
|
+
urls = result_set.results
|
22
|
+
|
23
|
+
feed_url = URI.join(config.fetch('root_url'), config.fetch('feed_file'))
|
24
|
+
body = Murlsh::AtomBody.new(config, req, feed_url, urls)
|
25
|
+
|
26
|
+
resp = Rack::Response.new(body, 200,
|
27
|
+
'Cache-Control' => 'must-revalidate, max-age=0',
|
28
|
+
'Content-Type' => 'application/atom+xml')
|
29
|
+
if u = body.updated
|
30
|
+
resp['Last-Modified'] = u.httpdate
|
31
|
+
end
|
32
|
+
resp
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :config
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/murlsh/auth.rb
CHANGED
@@ -44,6 +44,18 @@ module Murlsh
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
# Look up a user by email address.
|
48
|
+
#
|
49
|
+
# Return nil if not found.
|
50
|
+
def by_email(email)
|
51
|
+
hash = Digest::MD5.hexdigest(email)
|
52
|
+
|
53
|
+
self.class.csv_iter(@file) do |row|
|
54
|
+
return { :name => row[0], :email => row[1] } if hash == row[1]
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
47
59
|
end
|
48
60
|
|
49
61
|
end
|
data/lib/murlsh/build_query.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Murlsh
|
2
|
+
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Concatenate some files and return the result as a string.
|
6
|
+
def cat_files(files, sep=nil)
|
7
|
+
result = ''
|
8
|
+
files.each do |fname|
|
9
|
+
open(fname) do |h|
|
10
|
+
while (line = h.gets) do; result << line; end
|
11
|
+
result << sep if sep
|
12
|
+
end
|
13
|
+
end
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/murlsh/dispatch.rb
CHANGED
@@ -14,25 +14,34 @@ module Murlsh
|
|
14
14
|
def initialize(config)
|
15
15
|
@config = config
|
16
16
|
|
17
|
-
|
17
|
+
atom_server = Murlsh::AtomServer.new(config)
|
18
18
|
json_server = Murlsh::JsonServer.new(config)
|
19
|
+
m3u_server = Murlsh::M3uServer.new(config)
|
20
|
+
podcast_server = Murlsh::PodcastServer.new(config)
|
21
|
+
pop_server = Murlsh::PopServer.new(config)
|
22
|
+
random_server = Murlsh::RandomServer.new(config)
|
23
|
+
rss_server = Murlsh::RssServer.new(config)
|
24
|
+
url_server = Murlsh::UrlServer.new(config)
|
25
|
+
|
19
26
|
root_path = URI(config.fetch('root_url')).path
|
20
27
|
|
21
28
|
@routes = [
|
22
|
-
[%r{^HEAD #{root_path}
|
23
|
-
[%r{^GET #{root_path}
|
29
|
+
[%r{^(?:HEAD|GET) #{root_path}atom\.atom$}, atom_server.method(:get)],
|
30
|
+
[%r{^(?:HEAD|GET) #{root_path}json\.json$}, json_server.method(:get)],
|
31
|
+
[%r{^(?:HEAD|GET) #{root_path}m3u\.m3u$}, m3u_server.method(:get)],
|
32
|
+
[%r{^(?:HEAD|GET) #{root_path}podcast\.rss$}, podcast_server.method(:get)],
|
33
|
+
[%r{^POST #{root_path}pop$}, pop_server.method(:post)],
|
34
|
+
[%r{^(?:HEAD|GET) #{root_path}random$}, random_server.method(:get)],
|
35
|
+
[%r{^(?:HEAD|GET) #{root_path}rss\.rss$}, rss_server.method(:get)],
|
36
|
+
[%r{^(?:HEAD|GET) #{root_path}(url)?$}, url_server.method(:get)],
|
24
37
|
[%r{^POST #{root_path}(url)?$}, url_server.method(:post)],
|
25
|
-
[%r{^HEAD #{root_path}json\.json$}, json_server.method(:head)],
|
26
|
-
[%r{^GET #{root_path}json\.json$}, json_server.method(:get)],
|
27
38
|
]
|
28
39
|
|
29
40
|
db_init
|
30
41
|
end
|
31
42
|
|
32
43
|
def db_init
|
33
|
-
ActiveRecord::Base.establish_connection(
|
34
|
-
:adapter => 'sqlite3', :database => @config.fetch('db_file'))
|
35
|
-
|
44
|
+
ActiveRecord::Base.establish_connection config.fetch('db')
|
36
45
|
ActiveRecord::Base.default_timezone = :utc
|
37
46
|
ActiveRecord::Base.include_root_in_json = false
|
38
47
|
# ActiveRecord::Base.logger = Logger.new(STDERR)
|
@@ -55,13 +64,18 @@ module Murlsh
|
|
55
64
|
|
56
65
|
# Called if the request is not found.
|
57
66
|
def not_found(req)
|
58
|
-
|
67
|
+
if req.head?
|
68
|
+
Rack::Response.new([], 404)
|
69
|
+
else
|
70
|
+
Rack::Response.new("<p>#{req.url} not found</p>
|
59
71
|
|
60
|
-
<p><a href=\"#{
|
72
|
+
<p><a href=\"#{config.fetch('root_url')}\">root<a></p>
|
61
73
|
",
|
62
|
-
|
74
|
+
404, { 'Content-Type' => 'text/html' })
|
75
|
+
end
|
63
76
|
end
|
64
77
|
|
78
|
+
attr_reader :config
|
65
79
|
attr_accessor :routes
|
66
80
|
end
|
67
81
|
|
@@ -14,7 +14,7 @@ module Murlsh
|
|
14
14
|
headers = Rack::Utils::HeaderHash.new(headers)
|
15
15
|
|
16
16
|
if headers['ETag']
|
17
|
-
headers['ETag'].sub!
|
17
|
+
headers['ETag'].sub! /(")?$/, "#{headers['Content-Encoding']}\\1"
|
18
18
|
end
|
19
19
|
|
20
20
|
[status, headers, body]
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Murlsh
|
2
|
+
|
3
|
+
# Feed body mixin.
|
4
|
+
module FeedBody
|
5
|
+
|
6
|
+
# Content types to add an enclosure for.
|
7
|
+
EnclosureContentTypes = %w{
|
8
|
+
application/pdf
|
9
|
+
audio/mpeg
|
10
|
+
image/gif
|
11
|
+
image/jpeg
|
12
|
+
image/png
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(config, req, feed_url, urls)
|
16
|
+
@config, @req, @feed_url, @urls = config, req, feed_url, urls
|
17
|
+
@updated = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Yield body for Rack.
|
21
|
+
def each; yield build; end
|
22
|
+
|
23
|
+
# Build feed title.
|
24
|
+
def feed_title
|
25
|
+
result = "#{config['page_title']}"
|
26
|
+
req['q'] ? "#{result} /#{req['q']}" : result
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :config
|
30
|
+
attr_reader :req
|
31
|
+
attr_reader :feed_url
|
32
|
+
attr_reader :urls
|
33
|
+
attr_reader :updated
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/murlsh/img_store.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'cgi'
|
2
1
|
require 'digest/md5'
|
3
2
|
require 'open-uri'
|
4
|
-
require 'uri'
|
5
3
|
|
6
4
|
require 'RMagick'
|
7
5
|
|
@@ -9,66 +7,67 @@ require 'murlsh'
|
|
9
7
|
|
10
8
|
module Murlsh
|
11
9
|
|
12
|
-
#
|
10
|
+
# Store images from various sources in asset storage.
|
11
|
+
#
|
12
|
+
# Storage is determined by store_asset plugins.
|
13
13
|
class ImgStore
|
14
14
|
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
def initialize(
|
19
|
-
@
|
20
|
-
@user_agent = options[:user_agent]
|
15
|
+
# Store images from various sources in asset storage.
|
16
|
+
#
|
17
|
+
# Storage is determined by store_asset plugins.
|
18
|
+
def initialize(config)
|
19
|
+
@config = config
|
21
20
|
end
|
22
21
|
|
23
22
|
# Build headers to send with request.
|
24
23
|
def headers
|
25
24
|
result = {}
|
26
|
-
result['User-Agent'] =
|
25
|
+
result['User-Agent'] = config['user_agent'] if config['user_agent']
|
27
26
|
result
|
28
27
|
end
|
29
28
|
|
30
|
-
# Fetch an image from a url and store it
|
31
|
-
#
|
32
|
-
# The filename will be the md5sum of the contents plus the correct
|
33
|
-
# extension.
|
29
|
+
# Fetch an image from a url and store it in asset storage.
|
34
30
|
#
|
35
31
|
# If a block is given the Magick::ImageList created will be yielded
|
36
32
|
# before storage.
|
33
|
+
#
|
34
|
+
# Returns image url.
|
37
35
|
def store_url(url, &block)
|
38
|
-
open(url, headers) { |fin| store_img_data
|
36
|
+
open(url, headers) { |fin| store_img_data(fin.read, &block) }
|
39
37
|
end
|
40
38
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# The filename will be the md5sum of the contents plus the correct
|
44
|
-
# extension.
|
39
|
+
# Store a blob of image data in asset storage.
|
45
40
|
#
|
46
41
|
# If a block is given the Magick::ImageList created will be yielded
|
47
42
|
# before storage.
|
43
|
+
#
|
44
|
+
# Returns image url.
|
48
45
|
def store_img_data(img_data, &block)
|
49
46
|
img = Magick::ImageList.new.from_blob(img_data)
|
50
47
|
yield img if block_given?
|
51
48
|
store_img img
|
52
49
|
end
|
53
50
|
|
54
|
-
#
|
51
|
+
# Store a Magick::ImageList in asset storage.
|
55
52
|
#
|
56
|
-
#
|
57
|
-
# extension.
|
53
|
+
# Returns image url.
|
58
54
|
def store_img(img)
|
59
55
|
img.extend(Murlsh::ImageList) unless img.is_a?(Murlsh::ImageList)
|
60
56
|
img_data = img.to_blob
|
61
57
|
md5 = Digest::MD5.hexdigest(img_data)
|
62
58
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
59
|
+
name = "img/thumb/#{md5}#{img.preferred_extension}"
|
60
|
+
|
61
|
+
Murlsh::Plugin.hooks('store_asset') do |p|
|
62
|
+
# run until one returns something
|
63
|
+
if url = p.run(name, img_data, config)
|
64
|
+
return url
|
65
|
+
end
|
67
66
|
end
|
68
|
-
|
67
|
+
nil
|
69
68
|
end
|
70
69
|
|
71
|
-
attr_reader :
|
70
|
+
attr_reader :config
|
72
71
|
end
|
73
72
|
|
74
73
|
end
|
data/lib/murlsh/install.rb
CHANGED
data/lib/murlsh/json_body.rb
CHANGED
@@ -4,7 +4,6 @@ module Murlsh
|
|
4
4
|
|
5
5
|
# Recent urls json response builder.
|
6
6
|
class JsonBody
|
7
|
-
include Murlsh::BuildMd5
|
8
7
|
|
9
8
|
def initialize(config, req, result_set)
|
10
9
|
@config, @req, @result_set = config, req, result_set
|
@@ -19,6 +18,10 @@ module Murlsh
|
|
19
18
|
@body
|
20
19
|
else
|
21
20
|
urls = @result_set.results.map do |mu|
|
21
|
+
Murlsh::Plugin.hooks('url_display_pre') do |p|
|
22
|
+
p.run mu, @req, @config
|
23
|
+
end
|
24
|
+
|
22
25
|
h = mu.attributes
|
23
26
|
|
24
27
|
h['title'] = mu.title_stripped
|
@@ -26,7 +29,7 @@ module Murlsh
|
|
26
29
|
# add site root url to relative thumbnail urls
|
27
30
|
if h['thumbnail_url'] and
|
28
31
|
not URI(h['thumbnail_url']).scheme.to_s.downcase[/https?/]
|
29
|
-
h['thumbnail_url'] = URI.join(@config
|
32
|
+
h['thumbnail_url'] = URI.join(@config.fetch('root_url'),
|
30
33
|
h['thumbnail_url']).to_s
|
31
34
|
end
|
32
35
|
|
data/lib/murlsh/json_server.rb
CHANGED
@@ -5,8 +5,6 @@ module Murlsh
|
|
5
5
|
# Serve most recent urls in json and jsonp.
|
6
6
|
class JsonServer
|
7
7
|
|
8
|
-
include Murlsh::HeadFromGet
|
9
|
-
|
10
8
|
def initialize(config); @config = config; end
|
11
9
|
|
12
10
|
# Respond to a GET request. Return json of recent urls or jsonp if
|
@@ -14,26 +12,24 @@ module Murlsh
|
|
14
12
|
def get(req)
|
15
13
|
conditions = Murlsh::SearchConditions.new(req['q']).conditions
|
16
14
|
page = 1
|
17
|
-
per_page =
|
15
|
+
per_page = config.fetch('num_posts_feed', 25)
|
18
16
|
|
19
17
|
result_set = Murlsh::UrlResultSet.new(conditions, page, per_page)
|
20
18
|
|
21
|
-
resp = Rack::Response.new
|
22
|
-
|
23
19
|
if req['callback']
|
24
|
-
|
25
|
-
|
20
|
+
content_type = 'application/javascript'
|
21
|
+
body = Murlsh::JsonpBody.new(config, req, result_set)
|
26
22
|
else
|
27
|
-
|
28
|
-
|
23
|
+
content_type = 'application/json'
|
24
|
+
body = Murlsh::JsonBody.new(config, req, result_set)
|
29
25
|
end
|
30
26
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
resp
|
27
|
+
Rack::Response.new body, 200,
|
28
|
+
'Cache-Control' => 'must-revalidate, max-age=0',
|
29
|
+
'Content-Type' => content_type
|
35
30
|
end
|
36
31
|
|
32
|
+
attr_reader :config
|
37
33
|
end
|
38
34
|
|
39
35
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Murlsh
|
2
|
+
|
3
|
+
# m3u builder.
|
4
|
+
class M3uBody
|
5
|
+
include Murlsh::FeedBody
|
6
|
+
|
7
|
+
# m3u builder.
|
8
|
+
def build
|
9
|
+
if defined?(@body)
|
10
|
+
@body
|
11
|
+
else
|
12
|
+
result = "# #{feed_url}\r\n\r\n"
|
13
|
+
urls.each do |mu|
|
14
|
+
Murlsh::Plugin.hooks('url_display_pre') do |p|
|
15
|
+
p.run mu, req, config
|
16
|
+
end
|
17
|
+
|
18
|
+
result << "#{mu.url}\r\n"
|
19
|
+
@updated = @updated ? [@updated, mu.time].max : mu.time
|
20
|
+
end
|
21
|
+
|
22
|
+
@body = result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
require 'murlsh'
|
6
|
+
|
7
|
+
module Murlsh
|
8
|
+
|
9
|
+
# Serve m3u file of audio urls.
|
10
|
+
class M3uServer
|
11
|
+
|
12
|
+
AudioContentTypes = %w{
|
13
|
+
application/ogg
|
14
|
+
audio/mpeg
|
15
|
+
audio/ogg
|
16
|
+
}
|
17
|
+
|
18
|
+
def initialize(config); @config = config; end
|
19
|
+
|
20
|
+
# Respond to a GET request for m3u file.
|
21
|
+
def get(req)
|
22
|
+
conditions = ['content_type IN (?)', AudioContentTypes]
|
23
|
+
search_conditions = Murlsh::SearchConditions.new(req['q']).conditions
|
24
|
+
unless search_conditions.empty?
|
25
|
+
conditions[0] << " AND (#{search_conditions[0]})"
|
26
|
+
conditions.push(*search_conditions[1..-1])
|
27
|
+
end
|
28
|
+
|
29
|
+
page = 1
|
30
|
+
per_page = config.fetch('num_posts_feed', 25)
|
31
|
+
|
32
|
+
result_set = Murlsh::UrlResultSet.new(conditions, page, per_page)
|
33
|
+
urls = result_set.results
|
34
|
+
|
35
|
+
feed_url = URI.join(config.fetch('root_url'), 'm3u.m3u')
|
36
|
+
body = Murlsh::M3uBody.new(config, req, feed_url, urls)
|
37
|
+
|
38
|
+
resp = Rack::Response.new(body, 200,
|
39
|
+
'Cache-Control' => 'must-revalidate, max-age=0',
|
40
|
+
'Content-Type' => 'audio/x-mpegurl')
|
41
|
+
if u = body.updated
|
42
|
+
resp['Last-Modified'] = u.httpdate
|
43
|
+
end
|
44
|
+
resp
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :config
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/murlsh/markup.rb
CHANGED
@@ -70,7 +70,7 @@ module Murlsh
|
|
70
70
|
if options[:id]
|
71
71
|
if options[:label]
|
72
72
|
label_suffix = options[:label_suffix] || ':'
|
73
|
-
label
|
73
|
+
label "#{options[:label]}#{label_suffix}", :for => options[:id]
|
74
74
|
end
|
75
75
|
options[:name] ||= options[:id]
|
76
76
|
end
|
data/lib/murlsh/plugin.rb
CHANGED
@@ -9,8 +9,13 @@ module Murlsh
|
|
9
9
|
# run arguments (config hash)
|
10
10
|
# * avatar - called to get an avatar url from an email md5 sum
|
11
11
|
# run arguments (avatar url, url, config hash)
|
12
|
+
# * store_asset - store an asset somewhere where it can be loaded by url
|
13
|
+
# run arguments (name, data, config hash)
|
12
14
|
# * url_display_add - called to display additional information after urls
|
13
15
|
# run arguments (markup builder, url, config hash)
|
16
|
+
# * url_display_pre - called to modify a url on-the-fly before display, does
|
17
|
+
# not change database
|
18
|
+
# run arguments (url, rack request, config hash)
|
14
19
|
class Plugin
|
15
20
|
|
16
21
|
# Called when a plugin class inherits from this class (the way plugins
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
require 'murlsh'
|
6
|
+
|
7
|
+
module Murlsh
|
8
|
+
|
9
|
+
# Serve podcast RSS feed.
|
10
|
+
class PodcastServer
|
11
|
+
|
12
|
+
def initialize(config); @config = config; end
|
13
|
+
|
14
|
+
# Respond to a GET request for podcast RSS feed.
|
15
|
+
def get(req)
|
16
|
+
conditions = ['content_type = ?', 'audio/mpeg']
|
17
|
+
search_conditions = Murlsh::SearchConditions.new(req['q']).conditions
|
18
|
+
unless search_conditions.empty?
|
19
|
+
conditions[0] << " AND (#{search_conditions[0]})"
|
20
|
+
conditions.push(*search_conditions[1..-1])
|
21
|
+
end
|
22
|
+
|
23
|
+
page = 1
|
24
|
+
per_page = config.fetch('num_posts_feed', 25)
|
25
|
+
|
26
|
+
result_set = Murlsh::UrlResultSet.new(conditions, page, per_page)
|
27
|
+
urls = result_set.results
|
28
|
+
|
29
|
+
feed_url = URI.join(config.fetch('root_url'), 'podcast.rss')
|
30
|
+
body = Murlsh::RssBody.new(config, req, feed_url, urls)
|
31
|
+
|
32
|
+
resp = Rack::Response.new(body, 200,
|
33
|
+
'Cache-Control' => 'must-revalidate, max-age=0',
|
34
|
+
'Content-Type' => 'application/rss+xml')
|
35
|
+
if u = body.updated
|
36
|
+
resp['Last-Modified'] = u.httpdate
|
37
|
+
end
|
38
|
+
resp
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :config
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'net/pop'
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'json'
|
5
|
+
require 'postrank-uri'
|
6
|
+
require 'rack'
|
7
|
+
require 'rmail'
|
8
|
+
|
9
|
+
module Murlsh
|
10
|
+
|
11
|
+
# Pop mail from a pop3 server and add all urls in messages from murlsh users.
|
12
|
+
class PopServer
|
13
|
+
|
14
|
+
def initialize(config); @config = config; end
|
15
|
+
|
16
|
+
def post(req)
|
17
|
+
response_body = []
|
18
|
+
|
19
|
+
if req['secret'] == config['pop_secret']
|
20
|
+
Net::POP3.enable_ssl
|
21
|
+
Net::POP3.start(config.fetch('pop_server'), config.fetch('pop_port'),
|
22
|
+
config.fetch('pop_user'), config.fetch('pop_password')) do |pop|
|
23
|
+
pop.each_mail do |mail|
|
24
|
+
begin
|
25
|
+
response_body << process_mail(mail.pop)
|
26
|
+
rescue Exception
|
27
|
+
ensure
|
28
|
+
mail.delete
|
29
|
+
end
|
30
|
+
end
|
31
|
+
pop.finish
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Rack::Response.new response_body.to_json, 200,
|
36
|
+
'Content-Type' => 'application/json'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Authenticate the sender and add all urls extracted from the the email
|
40
|
+
# body.
|
41
|
+
def process_mail(mail)
|
42
|
+
parsed_mail = parse_mail(mail)
|
43
|
+
|
44
|
+
if user = Murlsh::Auth.new(config.fetch('auth_file')).by_email(
|
45
|
+
parsed_mail[:from])
|
46
|
+
parsed_mail[:uris].each do |uri|
|
47
|
+
mu = Murlsh::Url.new do |u|
|
48
|
+
u.url = uri
|
49
|
+
u.email = user[:email]
|
50
|
+
u.name = user[:name]
|
51
|
+
u.time = parsed_mail[:date]
|
52
|
+
end
|
53
|
+
|
54
|
+
# validate before add_pre plugins have run and also after (in save!)
|
55
|
+
raise ActiveRecord::RecordInvalid.new(mu) unless mu.valid?
|
56
|
+
Murlsh::Plugin.hooks('add_pre') { |p| p.run mu, config }
|
57
|
+
mu.save!
|
58
|
+
Murlsh::Plugin.hooks('add_post') { |p| p.run mu, config }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
parsed_mail
|
62
|
+
end
|
63
|
+
|
64
|
+
# Parse date, from address and urls found in body from an email message.
|
65
|
+
def parse_mail(mail)
|
66
|
+
parsed_mail = RMail::Parser.read(mail.gsub(/\r/, ''))
|
67
|
+
|
68
|
+
{
|
69
|
+
:date => parsed_mail.header.date.utc,
|
70
|
+
:from => parsed_mail.header.from.first.address.downcase,
|
71
|
+
:uris => PostRank::URI.extract(parsed_mail.body),
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :config
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
require 'murlsh'
|
4
|
+
|
5
|
+
module Murlsh
|
6
|
+
|
7
|
+
# Redirect to a random url from the database.
|
8
|
+
class RandomServer
|
9
|
+
|
10
|
+
def initialize(config); @config = config; end
|
11
|
+
|
12
|
+
# Redirect to a random url from the database optionally matching a query.
|
13
|
+
#
|
14
|
+
# Redirect to root url if no urls match.
|
15
|
+
def get(req)
|
16
|
+
if choice = random_url(Murlsh::SearchConditions.new(req['q']).conditions)
|
17
|
+
url = choice.url
|
18
|
+
else
|
19
|
+
url = config.fetch('root_url')
|
20
|
+
end
|
21
|
+
|
22
|
+
resp = Rack::Response.new("<a href=\"#{url}\">#{url}</a>")
|
23
|
+
resp.redirect(url)
|
24
|
+
|
25
|
+
resp
|
26
|
+
end
|
27
|
+
|
28
|
+
# Select a random url from the database optionally matching a query.
|
29
|
+
#
|
30
|
+
# Return nil if no urls match.
|
31
|
+
def random_url(conditions=[])
|
32
|
+
count = Murlsh::Url.count(:conditions => conditions)
|
33
|
+
if count > 0
|
34
|
+
Murlsh::Url.first(:conditions => conditions, :offset => rand(count))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :config
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|