ass 0.0.21 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +14 -2
  3. data/VERSION +1 -1
  4. data/ass.yml +3 -1
  5. data/lib/ass.rb +2 -404
  6. data/lib/ass/app.rb +277 -0
  7. data/lib/ass/conf.rb +165 -0
  8. metadata +57 -86
@@ -0,0 +1,277 @@
1
+ #encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'yaml'
5
+ require 'logger'
6
+ require 'sequel'
7
+ require 'socket'
8
+ require 'openssl'
9
+ require 'cgi'
10
+
11
+ require 'rufus/scheduler'
12
+ require 'eventmachine'
13
+ require 'sinatra'
14
+ require 'sinatra/base'
15
+ require 'sinatra/synchrony'
16
+ require 'rack/mobile-detect'
17
+
18
+ require 'uri'
19
+ require 'uri-handler'
20
+ require 'net/http'
21
+
22
+ require 'active_support'
23
+ require 'json'
24
+ require 'digest/sha2'
25
+
26
+ require 'will_paginate'
27
+ require 'will_paginate/sequel'
28
+
29
+ ############################################################
30
+ ## Apple Service Server based on Sinatra
31
+ ############################################################
32
+
33
+ class App < Sinatra::Base
34
+
35
+ register Sinatra::Synchrony
36
+
37
+ use Rack::MobileDetect
38
+
39
+ enable :logging
40
+
41
+ LOGGER = Logger.new("ass-#{$mode}.log", 'a+')
42
+
43
+ case $loglevel.upcase
44
+ when 'FATAL'
45
+ set :logging, Logger::FATAL
46
+ when 'ERROR'
47
+ set :logging, Logger::ERROR
48
+ when 'WARN'
49
+ set :logging, Logger::WARN
50
+ when 'INFO'
51
+ set :logging, Logger::INFO
52
+ when 'DEBUG'
53
+ set :logging, Logger::DEBUG
54
+ else
55
+ set :logging, Logger::DEBUG
56
+ end
57
+
58
+ if "#{$mode}".strip == 'production' then
59
+ set :environment, :production
60
+ else
61
+ set :environment, :development
62
+ end
63
+
64
+ set :root, File.expand_path('../../../', __FILE__)
65
+ set :port, "#{$port}".to_i
66
+ set :public_folder, File.dirname(__FILE__) + '/../../public'
67
+ set :views, File.dirname(__FILE__) + '/../../views'
68
+
69
+ helpers do
70
+
71
+ include Rack::Utils
72
+ alias_method :h, :escape_html
73
+
74
+ def connect_socket(app)
75
+ openSSLContext = $certkey["#{app}"]
76
+ sock = nil
77
+ sslSocket = nil;
78
+ if $mode == 'production' then
79
+ sock = TCPSocket.new('gateway.push.apple.com', 2195)
80
+ else
81
+ sock = TCPSocket.new('gateway.sandbox.push.apple.com', 2195)
82
+ end
83
+ sslSocket = OpenSSL::SSL::SSLSocket.new(sock, openSSLContext)
84
+ sslSocket.connect
85
+
86
+ [sock, sslSocket]
87
+ end
88
+
89
+ def logger
90
+ LOGGER
91
+ end
92
+
93
+ def checkFlood?(req)
94
+ if $client_ip != "#{req.ip}" then
95
+ $client_ip = "#{req.ip}"
96
+ return false
97
+ else
98
+ if $last_access == 0 then
99
+ return false
100
+ else
101
+ return isFlood?
102
+ end
103
+ end
104
+ end
105
+
106
+ def isFlood?
107
+ result = (Time.now - $last_access) < $flood * 60
108
+ $last_access = Time.now
109
+ return result
110
+ end
111
+
112
+ def iOS?
113
+ result = case request.env['X_MOBILE_DEVICE']
114
+ when /iPhone|iPod|iPad/ then
115
+ true
116
+ else false
117
+ end
118
+ return result
119
+ end
120
+
121
+ def authorized?
122
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
123
+ @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == ["#{$user}", "#{$pass}"]
124
+ end
125
+
126
+ def protected!
127
+ unless authorized?
128
+ response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
129
+ throw(:halt, [401, "Oops... we need your login name & password\n"])
130
+ end
131
+ end
132
+
133
+ def push(parameter)
134
+ message = CGI::unescape(parameter[:alert].encode("UTF-8") || "")[0..107]
135
+ pid = "#{parameter[:pid]}"
136
+ badge = 1
137
+ badge = parameter[:badge].to_i if parameter[:badge] and parameter[:badge] != ''
138
+ sound = CGI::unescape(parameter[:sound] || "")
139
+ extra = CGI::unescape(parameter[:extra] || "")
140
+
141
+ @tokens = Token.where(:app => "#{app}").reverse_order(:id)
142
+ @exist = Push.first(:pid => "#{pid}", :app => "#{app}")
143
+
144
+ unless @exist
145
+ Push.insert(:pid => pid,
146
+ :message => message,
147
+ :created_at => Time.now,
148
+ :app => "#{app}",
149
+ :ip => "#{parameter.ip}" )
150
+
151
+ sock, sslSocket = connect_socket("#{app}")
152
+
153
+ # write our packet to the stream
154
+ @tokens.each do |o|
155
+ begin
156
+ tokenText = "#{o[:token]}"
157
+ tokenData = [tokenText].pack('H*')
158
+ aps = {'aps'=> {}}
159
+ aps['aps']['alert'] = message
160
+ aps['aps']['badge'] = badge
161
+ aps['aps']['sound'] = sound
162
+ aps['aps']['extra'] = extra
163
+ pm = aps.to_json
164
+ packet = [0,0,32,devData,0,pm.bytesize,pm].pack("ccca*cca*")
165
+ sslSocket.write(packet)
166
+ rescue Errno::EPIPE, OpenSSL::SSL::SSLError => e
167
+ puts "e: #{e} from id:#{o[:id]}"
168
+ sleep 3
169
+ sock, sslSocket = connect_socket("#{app}")
170
+ next
171
+ #retry
172
+ end
173
+ end
174
+ # cleanup
175
+ sslSocket.close
176
+ sock.close
177
+ end
178
+ end
179
+
180
+ def localonly(req)
181
+ protected! unless req.host == 'localhost'
182
+ end
183
+
184
+ end
185
+
186
+ before do
187
+ if "#{$mode}".strip == 'development' then
188
+ enable :dump_errors, :raise_errors, :show_exceptions
189
+ else
190
+ disable :dump_errors, :raise_errors, :show_exceptions
191
+ end
192
+ end
193
+
194
+ get '/' do
195
+ erb :index
196
+ end
197
+
198
+ get '/about' do
199
+ erb :about
200
+ end
201
+
202
+ not_found do
203
+ erb :not_found
204
+ end
205
+
206
+ error do
207
+ @error = "";
208
+ @error = params['captures'].first.inspect if development?
209
+ end
210
+
211
+ post '/v1/send' do
212
+ app = params[:app]
213
+ message = CGI::escape(params[:message] || "").encode("UTF-8")
214
+ pid = "#{Time.now.to_i}"
215
+ # begin
216
+ # url = URI.parse("http://localhost:#{$port}/v1/apps/#{app}/push")
217
+ # post_args1 = { :alert => "#{message}".encode('UTF-8'), :pid => "#{pid}" }
218
+ # Net::HTTP.post_form(url, post_args1)
219
+ # rescue =>err
220
+ # puts "#{err.class} ##{err}"
221
+ # end
222
+ system "curl http://localhost:#{$port}/v1/apps/#{app}/push/#{message}/#{pid}"
223
+ redirect '/v1/admin/push' if (params[:app] and params[:message])
224
+ end
225
+
226
+ get "/v1/admin/:db" do
227
+ protected!
228
+ db = params[:db] || 'token'
229
+ page = 1
230
+ page = params[:page].to_i if params[:page]
231
+ if (db == 'token') then
232
+ @o = []
233
+ $apps.each_with_index { |app, index|
234
+ @o << Token.where(:app => app).order(:id).reverse.paginate(page, 20)
235
+ }
236
+ erb :token
237
+ elsif (db == 'push') then
238
+ @p = []
239
+ $apps.each_with_index { |app, index|
240
+ @p << Push.where(:app => app).order(:id).reverse.paginate(page, 20)
241
+ }
242
+ erb :push
243
+ else
244
+ erb :not_found
245
+ end
246
+ end
247
+
248
+ $apps.each { |app|
249
+
250
+ ## register token api
251
+ get "/v1/apps/#{app}/:token" do
252
+ if (("#{params[:token]}".length == 64) and iOS? and checkFlood?(request) ) then
253
+ o = Token.first(:app => app, :token => params[:token])
254
+ unless o
255
+ Token.insert(
256
+ :app => app,
257
+ :token => params[:token],
258
+ :created_at => Time.now
259
+ )
260
+ end
261
+ end
262
+ end
263
+
264
+ ## http POST method push api
265
+ post "/v1/apps/#{app}/push" do
266
+ localonly(request)
267
+ push(params)
268
+ end
269
+
270
+ ## http GET method push api
271
+ ## POST method get more options
272
+ get "/v1/apps/#{app}/push/:message/:pid" do
273
+ localonly(request)
274
+ push(params)
275
+ end
276
+ }
277
+ end
@@ -0,0 +1,165 @@
1
+ #encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'yaml'
5
+ require 'logger'
6
+ require 'sequel'
7
+ require 'socket'
8
+ require 'openssl'
9
+ require 'cgi'
10
+
11
+ require 'rufus/scheduler'
12
+
13
+ require 'uri'
14
+ require 'uri-handler'
15
+ require 'net/http'
16
+
17
+ require 'active_support'
18
+ require 'json'
19
+ require 'digest/sha2'
20
+
21
+ require 'will_paginate'
22
+ require 'will_paginate/sequel'
23
+
24
+ ############################################################
25
+ ## Initilization Setup
26
+ ############################################################
27
+ LIBDIR = File.expand_path(File.join(File.dirname(__FILE__), '../..', 'lib'))
28
+ ROOTDIR = File.expand_path(File.join(File.dirname(__FILE__), '../..'))
29
+ unless $LOAD_PATH.include?(LIBDIR)
30
+ $LOAD_PATH << LIBDIR
31
+ end
32
+
33
+ unless File.exist?("#{Dir.pwd}/ass.yml") then
34
+ puts 'create config file: ass.yml'
35
+ system "cp #{ROOTDIR}/ass.yml #{Dir.pwd}/ass.yml"
36
+ end
37
+
38
+ unless File.exist?("#{Dir.pwd}/cron") then
39
+ puts "create a demo 'cron' script"
40
+ system "cp #{ROOTDIR}/cron #{Dir.pwd}/cron"
41
+ end
42
+
43
+ ############################################################
44
+ ## Configuration Setup
45
+ ############################################################
46
+ env = ENV['SINATRA_ENV'] || "development"
47
+ config = YAML.load_file("#{Dir.pwd}/ass.yml")
48
+ $timer = "#{config['timer']}".to_i
49
+ $cron = config['cron'] || 'cron'
50
+ $port = "#{config['port']}".to_i || 4567
51
+ $mode = config['mode'] || env
52
+ $VERSION = File.open("#{ROOTDIR}/VERSION", "rb").read
53
+ $apps = config['apps'] || []
54
+ $log = config['log'] || 'off'
55
+ $user = config['user'] || 'admin'
56
+ $pass = config['pass'] || 'pass'
57
+ $pempass = config['pempass'] || ''
58
+ $loglevel = config['loglevel'] || 'info'
59
+ $flood = "#{config['flood']}".to_i || 1 # default 1 minute
60
+
61
+ $client_ip = '127.0.0.1'
62
+ $last_access = 0
63
+
64
+ ############################################################
65
+ ## Certificate Key Setup
66
+ ############################################################
67
+
68
+ $certkey = {}
69
+
70
+ def check_cert
71
+ $apps.each { |app|
72
+ unless File.exist?("#{Dir.pwd}/#{app}_#{$mode}.pem") then
73
+ puts "Please provide #{app}_#{$mode}.pem under '#{Dir.pwd}/' directory"
74
+ return false;
75
+ else
76
+ puts "'#{app}'s #{$mode} PEM: (#{app}_#{$mode}.pem)"
77
+ certfile = File.read("#{Dir.pwd}/#{app}_#{$mode}.pem")
78
+ openSSLContext = OpenSSL::SSL::SSLContext.new
79
+ openSSLContext.cert = OpenSSL::X509::Certificate.new(certfile)
80
+ if $pempass == '' then
81
+ openSSLContext.key = OpenSSL::PKey::RSA.new(certfile)
82
+ else
83
+ openSSLContext.key = OpenSSL::PKey::RSA.new(certfile,"#{$pempass}")
84
+ end
85
+ $certkey["#{app}"] = openSSLContext
86
+ end
87
+ }
88
+ return true
89
+ end
90
+
91
+ unless check_cert then
92
+ html = <<-END
93
+ 1: please provide certificate key pem file under current directory, name should be: appid_development.pem for development and appid_production.pem for production
94
+ 2: edit your ass.yml under current directory
95
+ 3: run ass
96
+ 4: iOS Client: in AppDelegate file, didRegisterForRemoteNotificationsWithDeviceToken method should access url below:
97
+ END
98
+ $apps.each { |app|
99
+ html << "'#{app}'s registration url: http://serverIP:#{$port}/v1/apps/#{app}/DeviceToken"
100
+ }
101
+ html << "5: Server: cron should access 'curl http://localhost:#{$port}/v1/app/push/{messages}/{pid}' to send push message"
102
+ puts html
103
+ exit
104
+ else
105
+ html = <<-END
106
+ #{'*'*80}
107
+ Apple Service Server(#{$VERSION}) is Running ...
108
+ Push Notification Service: Enabled
109
+ Mode: #{$mode}
110
+ Port: #{$port}
111
+ END
112
+ html << "#{'*'*80}"
113
+ html << "Cron Job: '#{Dir.pwd}/#{$cron}' script is running every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'} " unless "#{$timer}".to_i == 0
114
+ html << "\n"
115
+ html << "access http://localhost:#{$port}/ for more information"
116
+ html << "\n"
117
+ html << "#{'*'*80}"
118
+ puts html
119
+ end
120
+
121
+ ############################################################
122
+ ## Sequel Database Setup
123
+ ############################################################
124
+
125
+ unless File.exist?("#{Dir.pwd}/ass-#{$mode}.db") then
126
+ $DB = Sequel.connect("sqlite://#{Dir.pwd}/ass-#{$mode}.db")
127
+ $DB.create_table :tokens do
128
+ primary_key :id
129
+ String :app, :unique => false, :null => false
130
+ String :token, :unique => false, :null => false, :size => 100
131
+ Time :created_at
132
+ index [:app, :token]
133
+ end
134
+ $DB.create_table :pushes do
135
+ primary_key :id
136
+ String :pid, :unique => false, :null => false, :size => 100
137
+ String :app, :unique => false, :null => false, :size => 30
138
+ String :message, :unique => false, :null => false, :size => 107
139
+ String :ip, :unique => false, :null => false, :size => 20
140
+ Time :created_at
141
+ index [:pid, :app, :message]
142
+ end
143
+ else
144
+ $DB = Sequel.connect("sqlite://#{Dir.pwd}/ass-#{$mode}.db")
145
+ end
146
+
147
+ class Token < Sequel::Model
148
+ Sequel.extension :pagination
149
+ end
150
+
151
+ class Push < Sequel::Model
152
+ Sequel.extension :pagination
153
+ end
154
+
155
+ ############################################################
156
+ ## Timer Job Setup
157
+ ############################################################
158
+ scheduler = Rufus::Scheduler.start_new
159
+
160
+ unless $timer == 0 then
161
+ scheduler.every "#{$timer}m" do
162
+ puts "running job: '#{Dir.pwd}/#{$cron}' every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'}"
163
+ system "./#{$cron}"
164
+ end
165
+ end
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ass
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
5
- prerelease:
4
+ version: 0.0.24
6
5
  platform: ruby
7
6
  authors:
8
7
  - Eiffel Qiu
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-05-02 00:00:00.000000000 Z
11
+ date: 2013-10-23 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: sqlite3
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ! '>='
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ! '>='
28
25
  - !ruby/object:Gem::Version
@@ -30,7 +27,6 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: thin
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ! '>='
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ! '>='
44
39
  - !ruby/object:Gem::Version
@@ -46,7 +41,6 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: json
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ! '>='
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ! '>='
60
53
  - !ruby/object:Gem::Version
@@ -62,23 +55,20 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: sinatra
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ~>
59
+ - - ! '>='
68
60
  - !ruby/object:Gem::Version
69
- version: 1.3.0
61
+ version: '0'
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ~>
66
+ - - ! '>='
76
67
  - !ruby/object:Gem::Version
77
- version: 1.3.0
68
+ version: '0'
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: sequel
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
73
  - - ! '>='
84
74
  - !ruby/object:Gem::Version
@@ -86,7 +76,6 @@ dependencies:
86
76
  type: :runtime
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
80
  - - ! '>='
92
81
  - !ruby/object:Gem::Version
@@ -94,7 +83,6 @@ dependencies:
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: rufus-scheduler
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
87
  - - ! '>='
100
88
  - !ruby/object:Gem::Version
@@ -102,7 +90,6 @@ dependencies:
102
90
  type: :runtime
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
94
  - - ! '>='
108
95
  - !ruby/object:Gem::Version
@@ -110,7 +97,6 @@ dependencies:
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: eventmachine
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
101
  - - ! '>='
116
102
  - !ruby/object:Gem::Version
@@ -118,7 +104,6 @@ dependencies:
118
104
  type: :runtime
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
108
  - - ! '>='
124
109
  - !ruby/object:Gem::Version
@@ -126,23 +111,20 @@ dependencies:
126
111
  - !ruby/object:Gem::Dependency
127
112
  name: activesupport
128
113
  requirement: !ruby/object:Gem::Requirement
129
- none: false
130
114
  requirements:
131
115
  - - ! '>='
132
116
  - !ruby/object:Gem::Version
133
- version: 3.2.8
117
+ version: '0'
134
118
  type: :runtime
135
119
  prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
121
  requirements:
139
122
  - - ! '>='
140
123
  - !ruby/object:Gem::Version
141
- version: 3.2.8
124
+ version: '0'
142
125
  - !ruby/object:Gem::Dependency
143
126
  name: uri-handler
144
127
  requirement: !ruby/object:Gem::Requirement
145
- none: false
146
128
  requirements:
147
129
  - - ! '>='
148
130
  - !ruby/object:Gem::Version
@@ -150,7 +132,6 @@ dependencies:
150
132
  type: :runtime
151
133
  prerelease: false
152
134
  version_requirements: !ruby/object:Gem::Requirement
153
- none: false
154
135
  requirements:
155
136
  - - ! '>='
156
137
  - !ruby/object:Gem::Version
@@ -158,23 +139,20 @@ dependencies:
158
139
  - !ruby/object:Gem::Dependency
159
140
  name: will_paginate
160
141
  requirement: !ruby/object:Gem::Requirement
161
- none: false
162
142
  requirements:
163
- - - ~>
143
+ - - ! '>='
164
144
  - !ruby/object:Gem::Version
165
- version: '3.0'
145
+ version: '0'
166
146
  type: :runtime
167
147
  prerelease: false
168
148
  version_requirements: !ruby/object:Gem::Requirement
169
- none: false
170
149
  requirements:
171
- - - ~>
150
+ - - ! '>='
172
151
  - !ruby/object:Gem::Version
173
- version: '3.0'
152
+ version: '0'
174
153
  - !ruby/object:Gem::Dependency
175
154
  name: sinatra-contrib
176
155
  requirement: !ruby/object:Gem::Requirement
177
- none: false
178
156
  requirements:
179
157
  - - ! '>='
180
158
  - !ruby/object:Gem::Version
@@ -182,7 +160,6 @@ dependencies:
182
160
  type: :runtime
183
161
  prerelease: false
184
162
  version_requirements: !ruby/object:Gem::Requirement
185
- none: false
186
163
  requirements:
187
164
  - - ! '>='
188
165
  - !ruby/object:Gem::Version
@@ -190,7 +167,6 @@ dependencies:
190
167
  - !ruby/object:Gem::Dependency
191
168
  name: rack-mobile-detect
192
169
  requirement: !ruby/object:Gem::Requirement
193
- none: false
194
170
  requirements:
195
171
  - - ! '>='
196
172
  - !ruby/object:Gem::Version
@@ -198,7 +174,6 @@ dependencies:
198
174
  type: :runtime
199
175
  prerelease: false
200
176
  version_requirements: !ruby/object:Gem::Requirement
201
- none: false
202
177
  requirements:
203
178
  - - ! '>='
204
179
  - !ruby/object:Gem::Version
@@ -206,23 +181,20 @@ dependencies:
206
181
  - !ruby/object:Gem::Dependency
207
182
  name: sinatra-synchrony
208
183
  requirement: !ruby/object:Gem::Requirement
209
- none: false
210
184
  requirements:
211
- - - ~>
185
+ - - ! '>='
212
186
  - !ruby/object:Gem::Version
213
- version: 0.4.1
187
+ version: '0'
214
188
  type: :runtime
215
189
  prerelease: false
216
190
  version_requirements: !ruby/object:Gem::Requirement
217
- none: false
218
191
  requirements:
219
- - - ~>
192
+ - - ! '>='
220
193
  - !ruby/object:Gem::Version
221
- version: 0.4.1
194
+ version: '0'
222
195
  - !ruby/object:Gem::Dependency
223
196
  name: shoulda
224
197
  requirement: !ruby/object:Gem::Requirement
225
- none: false
226
198
  requirements:
227
199
  - - ! '>='
228
200
  - !ruby/object:Gem::Version
@@ -230,7 +202,6 @@ dependencies:
230
202
  type: :development
231
203
  prerelease: false
232
204
  version_requirements: !ruby/object:Gem::Requirement
233
- none: false
234
205
  requirements:
235
206
  - - ! '>='
236
207
  - !ruby/object:Gem::Version
@@ -238,23 +209,20 @@ dependencies:
238
209
  - !ruby/object:Gem::Dependency
239
210
  name: rdoc
240
211
  requirement: !ruby/object:Gem::Requirement
241
- none: false
242
212
  requirements:
243
- - - ~>
213
+ - - ! '>='
244
214
  - !ruby/object:Gem::Version
245
- version: '3.12'
215
+ version: '0'
246
216
  type: :development
247
217
  prerelease: false
248
218
  version_requirements: !ruby/object:Gem::Requirement
249
- none: false
250
219
  requirements:
251
- - - ~>
220
+ - - ! '>='
252
221
  - !ruby/object:Gem::Version
253
- version: '3.12'
222
+ version: '0'
254
223
  - !ruby/object:Gem::Dependency
255
224
  name: bundler
256
225
  requirement: !ruby/object:Gem::Requirement
257
- none: false
258
226
  requirements:
259
227
  - - ! '>='
260
228
  - !ruby/object:Gem::Version
@@ -262,7 +230,6 @@ dependencies:
262
230
  type: :development
263
231
  prerelease: false
264
232
  version_requirements: !ruby/object:Gem::Requirement
265
- none: false
266
233
  requirements:
267
234
  - - ! '>='
268
235
  - !ruby/object:Gem::Version
@@ -270,23 +237,20 @@ dependencies:
270
237
  - !ruby/object:Gem::Dependency
271
238
  name: jeweler
272
239
  requirement: !ruby/object:Gem::Requirement
273
- none: false
274
240
  requirements:
275
- - - ~>
241
+ - - ! '>='
276
242
  - !ruby/object:Gem::Version
277
- version: 1.8.4
243
+ version: '0'
278
244
  type: :development
279
245
  prerelease: false
280
246
  version_requirements: !ruby/object:Gem::Requirement
281
- none: false
282
247
  requirements:
283
- - - ~>
248
+ - - ! '>='
284
249
  - !ruby/object:Gem::Version
285
- version: 1.8.4
250
+ version: '0'
286
251
  - !ruby/object:Gem::Dependency
287
252
  name: sqlite3
288
253
  requirement: !ruby/object:Gem::Requirement
289
- none: false
290
254
  requirements:
291
255
  - - ! '>='
292
256
  - !ruby/object:Gem::Version
@@ -294,7 +258,6 @@ dependencies:
294
258
  type: :runtime
295
259
  prerelease: false
296
260
  version_requirements: !ruby/object:Gem::Requirement
297
- none: false
298
261
  requirements:
299
262
  - - ! '>='
300
263
  - !ruby/object:Gem::Version
@@ -302,7 +265,6 @@ dependencies:
302
265
  - !ruby/object:Gem::Dependency
303
266
  name: thin
304
267
  requirement: !ruby/object:Gem::Requirement
305
- none: false
306
268
  requirements:
307
269
  - - ! '>='
308
270
  - !ruby/object:Gem::Version
@@ -310,7 +272,6 @@ dependencies:
310
272
  type: :runtime
311
273
  prerelease: false
312
274
  version_requirements: !ruby/object:Gem::Requirement
313
- none: false
314
275
  requirements:
315
276
  - - ! '>='
316
277
  - !ruby/object:Gem::Version
@@ -318,7 +279,6 @@ dependencies:
318
279
  - !ruby/object:Gem::Dependency
319
280
  name: sinatra
320
281
  requirement: !ruby/object:Gem::Requirement
321
- none: false
322
282
  requirements:
323
283
  - - ! '>='
324
284
  - !ruby/object:Gem::Version
@@ -326,7 +286,6 @@ dependencies:
326
286
  type: :runtime
327
287
  prerelease: false
328
288
  version_requirements: !ruby/object:Gem::Requirement
329
- none: false
330
289
  requirements:
331
290
  - - ! '>='
332
291
  - !ruby/object:Gem::Version
@@ -334,7 +293,6 @@ dependencies:
334
293
  - !ruby/object:Gem::Dependency
335
294
  name: sequel
336
295
  requirement: !ruby/object:Gem::Requirement
337
- none: false
338
296
  requirements:
339
297
  - - ! '>='
340
298
  - !ruby/object:Gem::Version
@@ -342,7 +300,6 @@ dependencies:
342
300
  type: :runtime
343
301
  prerelease: false
344
302
  version_requirements: !ruby/object:Gem::Requirement
345
- none: false
346
303
  requirements:
347
304
  - - ! '>='
348
305
  - !ruby/object:Gem::Version
@@ -350,7 +307,6 @@ dependencies:
350
307
  - !ruby/object:Gem::Dependency
351
308
  name: rufus-scheduler
352
309
  requirement: !ruby/object:Gem::Requirement
353
- none: false
354
310
  requirements:
355
311
  - - ! '>='
356
312
  - !ruby/object:Gem::Version
@@ -358,7 +314,6 @@ dependencies:
358
314
  type: :runtime
359
315
  prerelease: false
360
316
  version_requirements: !ruby/object:Gem::Requirement
361
- none: false
362
317
  requirements:
363
318
  - - ! '>='
364
319
  - !ruby/object:Gem::Version
@@ -366,7 +321,6 @@ dependencies:
366
321
  - !ruby/object:Gem::Dependency
367
322
  name: eventmachine
368
323
  requirement: !ruby/object:Gem::Requirement
369
- none: false
370
324
  requirements:
371
325
  - - ! '>='
372
326
  - !ruby/object:Gem::Version
@@ -374,7 +328,6 @@ dependencies:
374
328
  type: :runtime
375
329
  prerelease: false
376
330
  version_requirements: !ruby/object:Gem::Requirement
377
- none: false
378
331
  requirements:
379
332
  - - ! '>='
380
333
  - !ruby/object:Gem::Version
@@ -382,7 +335,6 @@ dependencies:
382
335
  - !ruby/object:Gem::Dependency
383
336
  name: activesupport
384
337
  requirement: !ruby/object:Gem::Requirement
385
- none: false
386
338
  requirements:
387
339
  - - ! '>='
388
340
  - !ruby/object:Gem::Version
@@ -390,7 +342,6 @@ dependencies:
390
342
  type: :runtime
391
343
  prerelease: false
392
344
  version_requirements: !ruby/object:Gem::Requirement
393
- none: false
394
345
  requirements:
395
346
  - - ! '>='
396
347
  - !ruby/object:Gem::Version
@@ -398,7 +349,6 @@ dependencies:
398
349
  - !ruby/object:Gem::Dependency
399
350
  name: uri-handler
400
351
  requirement: !ruby/object:Gem::Requirement
401
- none: false
402
352
  requirements:
403
353
  - - ! '>='
404
354
  - !ruby/object:Gem::Version
@@ -406,7 +356,6 @@ dependencies:
406
356
  type: :runtime
407
357
  prerelease: false
408
358
  version_requirements: !ruby/object:Gem::Requirement
409
- none: false
410
359
  requirements:
411
360
  - - ! '>='
412
361
  - !ruby/object:Gem::Version
@@ -414,7 +363,6 @@ dependencies:
414
363
  - !ruby/object:Gem::Dependency
415
364
  name: will_paginate
416
365
  requirement: !ruby/object:Gem::Requirement
417
- none: false
418
366
  requirements:
419
367
  - - ~>
420
368
  - !ruby/object:Gem::Version
@@ -422,7 +370,6 @@ dependencies:
422
370
  type: :runtime
423
371
  prerelease: false
424
372
  version_requirements: !ruby/object:Gem::Requirement
425
- none: false
426
373
  requirements:
427
374
  - - ~>
428
375
  - !ruby/object:Gem::Version
@@ -430,7 +377,6 @@ dependencies:
430
377
  - !ruby/object:Gem::Dependency
431
378
  name: sinatra-contrib
432
379
  requirement: !ruby/object:Gem::Requirement
433
- none: false
434
380
  requirements:
435
381
  - - ! '>='
436
382
  - !ruby/object:Gem::Version
@@ -438,7 +384,34 @@ dependencies:
438
384
  type: :runtime
439
385
  prerelease: false
440
386
  version_requirements: !ruby/object:Gem::Requirement
441
- none: false
387
+ requirements:
388
+ - - ! '>='
389
+ - !ruby/object:Gem::Version
390
+ version: '0'
391
+ - !ruby/object:Gem::Dependency
392
+ name: rack-mobile-detect
393
+ requirement: !ruby/object:Gem::Requirement
394
+ requirements:
395
+ - - ! '>='
396
+ - !ruby/object:Gem::Version
397
+ version: '0'
398
+ type: :runtime
399
+ prerelease: false
400
+ version_requirements: !ruby/object:Gem::Requirement
401
+ requirements:
402
+ - - ! '>='
403
+ - !ruby/object:Gem::Version
404
+ version: '0'
405
+ - !ruby/object:Gem::Dependency
406
+ name: sinatra-synchrony
407
+ requirement: !ruby/object:Gem::Requirement
408
+ requirements:
409
+ - - ! '>='
410
+ - !ruby/object:Gem::Version
411
+ version: '0'
412
+ type: :runtime
413
+ prerelease: false
414
+ version_requirements: !ruby/object:Gem::Requirement
442
415
  requirements:
443
416
  - - ! '>='
444
417
  - !ruby/object:Gem::Version
@@ -458,6 +431,8 @@ files:
458
431
  - ass.yml
459
432
  - cron
460
433
  - lib/ass.rb
434
+ - lib/ass/app.rb
435
+ - lib/ass/conf.rb
461
436
  - public/css/bootstrap-responsive.css
462
437
  - public/css/bootstrap-responsive.min.css
463
438
  - public/css/bootstrap.css
@@ -478,29 +453,25 @@ files:
478
453
  homepage: http://github.com/eiffelqiu/ass
479
454
  licenses:
480
455
  - MIT
456
+ metadata: {}
481
457
  post_install_message:
482
458
  rdoc_options: []
483
459
  require_paths:
484
460
  - lib
485
461
  required_ruby_version: !ruby/object:Gem::Requirement
486
- none: false
487
462
  requirements:
488
463
  - - ! '>='
489
464
  - !ruby/object:Gem::Version
490
465
  version: '0'
491
- segments:
492
- - 0
493
- hash: -3291505526784258788
494
466
  required_rubygems_version: !ruby/object:Gem::Requirement
495
- none: false
496
467
  requirements:
497
468
  - - ! '>='
498
469
  - !ruby/object:Gem::Version
499
470
  version: '0'
500
471
  requirements: []
501
472
  rubyforge_project: ass
502
- rubygems_version: 1.8.24
473
+ rubygems_version: 2.1.9
503
474
  signing_key:
504
- specification_version: 3
475
+ specification_version: 4
505
476
  summary: Apple Service Server
506
477
  test_files: []