departr 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +30 -0
- data/config.ru.example +16 -0
- data/departr.gemspec +23 -0
- data/lib/departr/commands.rb +56 -0
- data/lib/departr/config.rb +65 -0
- data/lib/departr/server.rb +199 -0
- data/lib/departr/session.rb +40 -0
- data/lib/departr/settings.rb +37 -0
- data/lib/departr.rb +24 -0
- data/lib/jsmin.rb +280 -0
- data/lib/rpx.rb +102 -0
- data/public/favicon.ico +0 -0
- data/public/javascripts/1_mootools.js +5901 -0
- data/public/javascripts/2_autocomplete.js +176 -0
- data/public/javascripts/3_simple-modal.js +422 -0
- data/public/javascripts/4_coolclock.js +325 -0
- data/public/javascripts/5_appliction.js +144 -0
- data/public/javascripts/6_command.js +288 -0
- data/views/help.haml +58 -0
- data/views/index.haml +71 -0
- data/views/layout.haml +11 -0
- data/views/profile.haml +1 -0
- data/views/settings.haml +45 -0
- data/views/style.sass +490 -0
- metadata +124 -0
data/Gemfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gemspec
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
departr (0.1)
|
5
|
+
haml (~> 3.1)
|
6
|
+
json (~> 1.6)
|
7
|
+
sass (~> 3.1)
|
8
|
+
sinatra (~> 1.3)
|
9
|
+
tzinfo (~> 0.3)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
specs:
|
13
|
+
haml (3.1.4)
|
14
|
+
json (1.6.5)
|
15
|
+
rack (1.4.0)
|
16
|
+
rack-protection (1.2.0)
|
17
|
+
rack
|
18
|
+
sass (3.1.12)
|
19
|
+
sinatra (1.3.2)
|
20
|
+
rack (~> 1.3, >= 1.3.6)
|
21
|
+
rack-protection (~> 1.2)
|
22
|
+
tilt (~> 1.3, >= 1.3.3)
|
23
|
+
tilt (1.3.3)
|
24
|
+
tzinfo (0.3.31)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
departr!
|
data/config.ru.example
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require File.join(File.dirname(__FILE__), "lib", "departr")
|
3
|
+
|
4
|
+
app = Departr::Server.new do
|
5
|
+
set :environment, :production
|
6
|
+
set :data_path, File.join(File.dirname(__FILE__), "data")
|
7
|
+
|
8
|
+
set :rpx, {
|
9
|
+
:api_key => '........................................',
|
10
|
+
:base_url => 'https://rpxnow.com',
|
11
|
+
:realm => '................'
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
run app
|
16
|
+
|
data/departr.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "departr"
|
5
|
+
s.version = 0.2
|
6
|
+
s.date = '2012-01-24'
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.author = "Florent Solt"
|
9
|
+
s.email = "florent@solt.biz"
|
10
|
+
s.homepage = "https://github.com/florentsolt/departr"
|
11
|
+
s.summary = "Departr is a smart and fast startpage to help you reach other web sites."
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_runtime_dependency("json", ["~> 1.6"])
|
19
|
+
s.add_runtime_dependency("tzinfo", ["~> 0.3"])
|
20
|
+
s.add_runtime_dependency("haml", ["~> 3.1"])
|
21
|
+
s.add_runtime_dependency("sass", ["~> 3.1"])
|
22
|
+
s.add_runtime_dependency("sinatra", ["~> 1.3"])
|
23
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Departr
|
2
|
+
module Command
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def filename(provider, user)
|
6
|
+
File.join(Config.data_path, provider, user, 'commands')
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(provider, user)
|
10
|
+
JSON.parse(File.read(filename(provider, user))) rescue sort(Config.commands)
|
11
|
+
end
|
12
|
+
|
13
|
+
def time(provider, user)
|
14
|
+
if File.exists? filename(provider, user)
|
15
|
+
File.mtime(filename(provider, user))
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def etag(provider, user)
|
22
|
+
begin
|
23
|
+
Digest::MD5.hexdigest(time(provider, user).to_s)
|
24
|
+
rescue
|
25
|
+
'-no-command-file-'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def default
|
30
|
+
Config.commands
|
31
|
+
end
|
32
|
+
|
33
|
+
def add(provider, user, command)
|
34
|
+
save(provider, user, get(provider, user) + [command])
|
35
|
+
command
|
36
|
+
end
|
37
|
+
|
38
|
+
def save(provider, user, commands)
|
39
|
+
File.open(filename(provider, user), 'w') do |fd|
|
40
|
+
fd.write sort(commands).to_json
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def revert(provider, user)
|
45
|
+
File.unlink(filename(provider, user))
|
46
|
+
end
|
47
|
+
|
48
|
+
def sort(commands)
|
49
|
+
commands.sort do |a,b|
|
50
|
+
a = a['name'].gsub(/\{\w+\}/, '')
|
51
|
+
b = b['name'].gsub(/\{\w+\}/, '')
|
52
|
+
a <=> b
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Departr
|
2
|
+
module Config
|
3
|
+
extend self
|
4
|
+
|
5
|
+
@data = {
|
6
|
+
:data_path => "/tmp",
|
7
|
+
|
8
|
+
# Default settings
|
9
|
+
|
10
|
+
:open_in_new_page => false,
|
11
|
+
:default_search => "http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=",
|
12
|
+
|
13
|
+
:github => true,
|
14
|
+
:calendar => true,
|
15
|
+
:clock1 => "Europe/Paris",
|
16
|
+
:clock2 => "America/New_York",
|
17
|
+
:clock3 => "America/Los_Angeles",
|
18
|
+
|
19
|
+
# Default commands
|
20
|
+
|
21
|
+
:commands => [
|
22
|
+
{'name' => "google search for {words}", 'url' => "http://www.google.com/search?rls=en&q={words}&ie=UTF-8&oe=UTF-8"},
|
23
|
+
{'name' => "google search for {words} in {lang}", 'url' => "http://www.google.com/search?rls={lang}&q={words}&ie=UTF-8&oe=UTF-8"},
|
24
|
+
{'name' => "wikipedia search for {words}", 'url' => "http://en.wikipedia.org/wiki/{words}"},
|
25
|
+
{'name' => "twitter search for {words}", 'url' => "http://search.twitter.com/search?q={words}"},
|
26
|
+
{'name' => "youtube search for {words}", 'url' => "http://www.youtube.com/results?search_query={words}"},
|
27
|
+
{'name' => "dailymotion search for {words}", 'url' => "http://www.dailymotion.com/relevance/search/{words}"},
|
28
|
+
{'name' => "facebook search for {words}", 'url' => "http://www.facebook.com/search/?q={words}"},
|
29
|
+
{'name' => "news search for {words}", 'url' => "http://news.google.com/news/search?q={words}"},
|
30
|
+
{'name' => "images search for {words}", 'url' => "http://images.google.com/images?q={words}"},
|
31
|
+
{'name' => "maps to {where}", 'url' => "http://maps.google.com?q={where}"},
|
32
|
+
{'name' => "translate {words} in french", 'url' => "http://www.google.com/translate_t?langpair=en|fr&q={words}"},
|
33
|
+
{'name' => "translate {words} in english", 'url' => "http://www.google.com/translate_t?langpair=fr|en&q={words}"},
|
34
|
+
{'name' => "php search for {word}", 'url' => "http://www.php.net/{word}"},
|
35
|
+
{'name' => "delicious search for {tag}", 'url' => "http://delicious.com/tag/{tag}"},
|
36
|
+
{'name' => "flickr search for {tag}", 'url' => "http://www.flickr.com/photos/tags/{tag}"},
|
37
|
+
{'name' => "lifehacker search for {words}", 'url' => "http://lifehacker.com/search/{words}/"},
|
38
|
+
{'name' => "techcrunch search for {words}", 'url' => "http://search.techcrunch.com/query.php?s={words}"},
|
39
|
+
{'name' => "down for everyone or just me {domain}", 'url' => "http://downforeveryoneorjustme.com/{domain}"},
|
40
|
+
{'name' => "netvibes", 'url' => "http://www.netvibes.com/"},
|
41
|
+
{'name' => "flickr", 'url' => "http://www.flickr.com/"},
|
42
|
+
{'name' => "facebook", 'url' => "http://www.facebook.com"},
|
43
|
+
{'name' => "twitter", 'url' => "http://www.twitter.com"},
|
44
|
+
{'name' => "gist", 'url' => "https://gist.github.com"}
|
45
|
+
]
|
46
|
+
}
|
47
|
+
|
48
|
+
def set(key, val = nil, &blk)
|
49
|
+
if val.is_a? Hash and @data.key? key
|
50
|
+
@data[key].update val
|
51
|
+
else
|
52
|
+
@data[key] = block_given? ? blk : val
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def get(key)
|
57
|
+
@data[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(name, *args, &blk)
|
61
|
+
@data[name]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Departr
|
4
|
+
class Server < Sinatra::Base
|
5
|
+
|
6
|
+
#-----------------------------------------------------------------------------
|
7
|
+
# Constructor
|
8
|
+
#-----------------------------------------------------------------------------
|
9
|
+
|
10
|
+
def initialize(*args, &blk)
|
11
|
+
super
|
12
|
+
Departr::Config.instance_eval(&blk) if block_given?
|
13
|
+
@config = Config
|
14
|
+
|
15
|
+
# when called in the constructor, it remains persistant
|
16
|
+
# if not, it's rebuild every time
|
17
|
+
javascripts! if production?
|
18
|
+
end
|
19
|
+
|
20
|
+
def javascripts!
|
21
|
+
return @javascripts if not @javascripts.nil?
|
22
|
+
@javascripts = ''
|
23
|
+
Dir[File.join(settings.root, 'public', 'javascripts', '*.js')].sort.delete_if do |file|
|
24
|
+
not File.basename(file).match(/^\d+_/)
|
25
|
+
end.each do |file|
|
26
|
+
@javascripts << "\n/* #{File.basename(file)} */\n"
|
27
|
+
@javascripts << File.read(file)
|
28
|
+
end
|
29
|
+
@javascripts_checksum = Digest::SHA1.hexdigest(@javascripts)
|
30
|
+
@javascripts = JSMin.minify(@javascripts) if production?
|
31
|
+
end
|
32
|
+
|
33
|
+
#-----------------------------------------------------------------------------
|
34
|
+
# Sinatra settings
|
35
|
+
#-----------------------------------------------------------------------------
|
36
|
+
|
37
|
+
set :root, File.join(File.dirname(__FILE__), '..', '..')
|
38
|
+
|
39
|
+
set :sass, {
|
40
|
+
:cache_store => Sass::CacheStores::Memory.new,
|
41
|
+
:style => production? ? :compressed : :expanded
|
42
|
+
}
|
43
|
+
|
44
|
+
set :haml, {
|
45
|
+
:format => :xhtml,
|
46
|
+
:ugly => production?
|
47
|
+
}
|
48
|
+
|
49
|
+
set :reload_templates, true if development?
|
50
|
+
|
51
|
+
#-----------------------------------------------------------------------------
|
52
|
+
# Helpers
|
53
|
+
#-----------------------------------------------------------------------------
|
54
|
+
|
55
|
+
helpers do
|
56
|
+
include Rack::Utils
|
57
|
+
alias_method :h, :escape_html
|
58
|
+
|
59
|
+
def host
|
60
|
+
"http://#{request.host}#{request.port == 80 ? '' : ':' + request.port.to_s}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def auth!
|
64
|
+
throw(:halt, [401, "Not authorized\n"]) if not auth?
|
65
|
+
end
|
66
|
+
|
67
|
+
def auth?
|
68
|
+
@provider = request.cookies["provider"]
|
69
|
+
@user = request.cookies["user"]
|
70
|
+
@session = request.cookies["session"]
|
71
|
+
check = Session.valid?(@provider, @user, @session)
|
72
|
+
if !check
|
73
|
+
puts "Invalid session for #{@provider.inspect}, #{@user.inspect}, #{@session.inspect}"
|
74
|
+
end
|
75
|
+
check
|
76
|
+
end
|
77
|
+
|
78
|
+
def clock_label(tz)
|
79
|
+
return ''.to_json if not tz
|
80
|
+
tz.split('/').last.gsub('_', ' ').to_json
|
81
|
+
end
|
82
|
+
|
83
|
+
def clock_offset(tz)
|
84
|
+
return 0 if not tz
|
85
|
+
TZInfo::Timezone.get(tz).current_period.utc_total_offset / 3600
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#-----------------------------------------------------------------------------
|
90
|
+
# Javascripts and stylesheets
|
91
|
+
#-----------------------------------------------------------------------------
|
92
|
+
|
93
|
+
get '/javascripts/all.js' do
|
94
|
+
content_type :js
|
95
|
+
javascripts!
|
96
|
+
etag "js-#{@javascripts_checksum}" if production?
|
97
|
+
@javascripts
|
98
|
+
end
|
99
|
+
|
100
|
+
get '/stylesheets/all.css' do
|
101
|
+
content_type :css
|
102
|
+
if production?
|
103
|
+
time = File.mtime(File.join(settings.root, 'views', 'style.sass'))
|
104
|
+
etag "css-#{time.to_i}"
|
105
|
+
end
|
106
|
+
sass :style
|
107
|
+
end
|
108
|
+
|
109
|
+
#-----------------------------------------------------------------------------
|
110
|
+
# Session
|
111
|
+
#-----------------------------------------------------------------------------
|
112
|
+
|
113
|
+
post '/signin' do
|
114
|
+
if @config.get(:rpx)
|
115
|
+
token = params[:token]
|
116
|
+
rpx = Rpx::RpxHelper.new(@config.get(:rpx)[:api_key], @config.get(:rpx)[:base_url], @config.get(:rpx)[:realm])
|
117
|
+
profile = rpx.auth_info(token, request.url)
|
118
|
+
user, session = Session.signin(profile)
|
119
|
+
response.set_cookie("session", :value => session, :path => '/', :expires => Time.now + 60*60*24*365)
|
120
|
+
response.set_cookie("user", :value => user, :path => '/', :expires => Time.now + 60*60*24*365)
|
121
|
+
response.set_cookie("name", :value => profile['preferredUsername'], :path => '/', :expires => Time.now + 60*60*24*365)
|
122
|
+
response.set_cookie("provider", :value => profile['providerName'].downcase, :path => '/', :expires => Time.now + 60*60*24*365)
|
123
|
+
end
|
124
|
+
redirect '/'
|
125
|
+
end
|
126
|
+
|
127
|
+
get '/profile' do
|
128
|
+
auth!
|
129
|
+
Session.profile(@provider, @user, false).to_json
|
130
|
+
end
|
131
|
+
|
132
|
+
#-----------------------------------------------------------------------------
|
133
|
+
# Settings
|
134
|
+
#-----------------------------------------------------------------------------
|
135
|
+
|
136
|
+
get '/settings' do
|
137
|
+
auth!
|
138
|
+
haml :settings, :layout => false
|
139
|
+
end
|
140
|
+
|
141
|
+
post '/settings' do
|
142
|
+
auth!
|
143
|
+
Settings.save(@provider, @user, JSON.parse(request.body.read))
|
144
|
+
status 200
|
145
|
+
end
|
146
|
+
|
147
|
+
#-----------------------------------------------------------------------------
|
148
|
+
# Commands
|
149
|
+
#-----------------------------------------------------------------------------
|
150
|
+
|
151
|
+
get '/command/revert' do
|
152
|
+
auth!
|
153
|
+
@commands = Command.revert(@provider, @user)
|
154
|
+
redirect '/'
|
155
|
+
end
|
156
|
+
|
157
|
+
post '/command/save' do
|
158
|
+
auth!
|
159
|
+
@commands = Command.save(@provider, @user, JSON.parse(request.body.read))
|
160
|
+
status 200
|
161
|
+
end
|
162
|
+
|
163
|
+
post '/command/add' do
|
164
|
+
auth!
|
165
|
+
@commands = Command.add(@provider, @user, JSON.parse(request.body.read))
|
166
|
+
status 200
|
167
|
+
end
|
168
|
+
|
169
|
+
get '/command/all' do
|
170
|
+
auth!
|
171
|
+
content_type :json
|
172
|
+
etag "command-" + Command.etag(@provider, @user) if production?
|
173
|
+
Command.get(@provider, @user).to_json
|
174
|
+
end
|
175
|
+
|
176
|
+
#-----------------------------------------------------------------------------
|
177
|
+
# Index
|
178
|
+
#-----------------------------------------------------------------------------
|
179
|
+
|
180
|
+
get '/help' do
|
181
|
+
haml :help
|
182
|
+
end
|
183
|
+
|
184
|
+
get '/' do
|
185
|
+
if auth?
|
186
|
+
etag "index-#{Command.etag(@provider, @user)}-#{Settings.etag(@provider, @user)}" if production?
|
187
|
+
@commands = Command.get(@provider, @user)
|
188
|
+
@settings = Settings.get(@provider, @user)
|
189
|
+
else
|
190
|
+
etag "default-#{Digest::SHA1.hexdigest(Command.default(false))}" if production?
|
191
|
+
response.delete_cookie("user")
|
192
|
+
response.delete_cookie("session")
|
193
|
+
@commands = Command.default
|
194
|
+
@settings = Settings.default
|
195
|
+
end
|
196
|
+
haml :index
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Departr
|
2
|
+
module Session
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def filename(provider, user, session_or_profile)
|
6
|
+
if session_or_profile == :profile
|
7
|
+
file = File.join(Config.data_path, provider, user, 'profile')
|
8
|
+
else
|
9
|
+
file = File.join(Config.data_path, provider, user, 'session', session_or_profile)
|
10
|
+
end
|
11
|
+
FileUtils.mkdir_p File.dirname(file) if not File.directory? File.dirname(file)
|
12
|
+
file
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?(provider, user, session)
|
16
|
+
begin
|
17
|
+
File.exists? filename(provider, user, session)
|
18
|
+
rescue
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def signin(profile)
|
24
|
+
user = Digest::SHA1.hexdigest(profile['identifier'])
|
25
|
+
provider = profile['providerName'].downcase
|
26
|
+
File.open(filename(provider, user, :profile), 'w') do |fd|
|
27
|
+
fd.write profile.to_json
|
28
|
+
end
|
29
|
+
session = Digest::SHA1.hexdigest("#{profile['identifier']}#{Time.now}#{rand}")
|
30
|
+
File.open(filename(provider, user, session), 'w') do |fd|
|
31
|
+
fd.write Time.now.to_s
|
32
|
+
end
|
33
|
+
[user, session]
|
34
|
+
end
|
35
|
+
|
36
|
+
def profile(provider, user)
|
37
|
+
JSON.parse(File.read(filename(provider, user, :profile)))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Departr
|
2
|
+
module Settings
|
3
|
+
extend self
|
4
|
+
|
5
|
+
KEYS = [
|
6
|
+
:default_search, :open_in_new_page,
|
7
|
+
:github, :calendar,
|
8
|
+
:clock1, :clock2, :clock3
|
9
|
+
]
|
10
|
+
|
11
|
+
def filename(provider, user)
|
12
|
+
File.join(Config.data_path, provider, user, 'settings')
|
13
|
+
end
|
14
|
+
|
15
|
+
def etag(provider, user)
|
16
|
+
Digest::MD5.hexdigest(get(provider, user).to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(provider, user)
|
20
|
+
default.merge(JSON.parse(File.read(filename(provider, user)))) rescue default
|
21
|
+
end
|
22
|
+
|
23
|
+
def save(provider, user, data)
|
24
|
+
File.open(filename(provider, user), 'w') do |fd|
|
25
|
+
fd.write data.to_json
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def default
|
30
|
+
hash = {}
|
31
|
+
# Do not keep symbol because it's save in JSON
|
32
|
+
KEYS.each { |key| hash[key.to_s] = Config.get(key) }
|
33
|
+
hash
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/departr.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
if defined? Encoding
|
2
|
+
Encoding.default_external = Encoding::UTF_8
|
3
|
+
Encoding.default_internal = Encoding::UTF_8
|
4
|
+
end
|
5
|
+
|
6
|
+
$: << File.dirname(__FILE__) unless $:.include? File.dirname(__FILE__)
|
7
|
+
require "digest/sha1"
|
8
|
+
require "json"
|
9
|
+
require "tzinfo"
|
10
|
+
require "haml"
|
11
|
+
require "sass"
|
12
|
+
require "fileutils"
|
13
|
+
require "net/http"
|
14
|
+
require "sinatra"
|
15
|
+
|
16
|
+
# TODO: use gems
|
17
|
+
require "rpx"
|
18
|
+
require "jsmin"
|
19
|
+
|
20
|
+
require "departr/server"
|
21
|
+
require "departr/config"
|
22
|
+
require "departr/settings"
|
23
|
+
require "departr/session"
|
24
|
+
require "departr/commands"
|