me_sd 0.0.3beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/me_sd.rb +384 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 941dc0362e5bc77e988b58c63f85819035a7b4a1
|
4
|
+
data.tar.gz: d67a49a6fc43bd400f51a08770a3c613e69f049c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5fe673eefd73e36425110010575f8e4fccc89a6de38c045843e24d2439b69ba54ffcf48fb97a9e26eb932a222e42df98d83131ed81fa81123f66f41de735bb7b
|
7
|
+
data.tar.gz: 85f5debddf474d137ee6e29574a23a0c943ddca418f4ce70b99ad0d96e12a58fb1fcded640e40610953012debe34b2995e4c61f57ad77322565e515582a55661
|
data/lib/me_sd.rb
ADDED
@@ -0,0 +1,384 @@
|
|
1
|
+
# sd = MESD.new({ host: "192.168.0.150", port: "8080", username: "user", password: "P@ssw0rd" })
|
2
|
+
# # default port is "80"
|
3
|
+
# => true
|
4
|
+
# unless sd.errors
|
5
|
+
# requests = sd.get_all_requests
|
6
|
+
# requests[0].data
|
7
|
+
# end
|
8
|
+
# => #<MESD::Request:0x0000000265d360 @id="29", ..., @description="request decription", @resolution="request resolution", ...>
|
9
|
+
# request = Request.new({ session: sd.session, id: 29 })
|
10
|
+
# request.data(:name, :resolution)
|
11
|
+
# => #<MESD::Request:0x000000023b6800 @id="29", ..., @name="request name", @resolution="request resolution">
|
12
|
+
# request.get_resolution
|
13
|
+
# => "request resolution"
|
14
|
+
|
15
|
+
class MESD
|
16
|
+
attr_accessor :session, :last_error, :curobj, :current_body
|
17
|
+
|
18
|
+
require "net/http"
|
19
|
+
EXCEPTIONS = [Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::EHOSTUNREACH, EOFError,
|
20
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError]
|
21
|
+
|
22
|
+
def initialize(args)
|
23
|
+
host = args[:host]
|
24
|
+
port = args[:port] || "80"
|
25
|
+
username = args[:username]
|
26
|
+
password = args[:password]
|
27
|
+
uri = URI("http://#{host}:#{port}")
|
28
|
+
begin
|
29
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
30
|
+
request = http.get(uri)
|
31
|
+
cookie = request.response["set-cookie"]
|
32
|
+
uri = "#{uri}/j_security_check"
|
33
|
+
auth_data = ""\
|
34
|
+
"j_username=#{username}&"\
|
35
|
+
"j_password=#{password}&"\
|
36
|
+
"AdEnable=false&"\
|
37
|
+
"DomainCount=0&"\
|
38
|
+
"LDAPEnable=false&"\
|
39
|
+
"LocalAuth=No&"\
|
40
|
+
"LocalAuthWithDomain=No&"\
|
41
|
+
"dynamicUserAddition_status=true&"\
|
42
|
+
"hidden=Select+a+Domain&"\
|
43
|
+
"hidden=For+Domain&"\
|
44
|
+
"localAuthEnable=true&"\
|
45
|
+
"loginButton=Login&"\
|
46
|
+
"logonDomainName=-1&"\
|
47
|
+
""
|
48
|
+
auth_headers = {
|
49
|
+
"Referer" => "http://#{host}:#{port}",
|
50
|
+
"Host" => "#{host}:#{port}",
|
51
|
+
"Cookie" => "#{cookie};",
|
52
|
+
}
|
53
|
+
request = http.post(uri, auth_data, auth_headers)
|
54
|
+
@session = {
|
55
|
+
host: host,
|
56
|
+
port: port,
|
57
|
+
cookie: cookie,
|
58
|
+
}
|
59
|
+
@last_error = "wrong credentials" unless self.session_healthy?(self.session)
|
60
|
+
end
|
61
|
+
rescue *EXCEPTIONS => @last_error
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# logs in and tries to find out is session healthy
|
66
|
+
# criteria: logout button is present
|
67
|
+
def session_healthy?(session)
|
68
|
+
return false unless session
|
69
|
+
session_healthy = false
|
70
|
+
uri = URI("http://#{session[:host]}:#{session[:port]}/MySchedule.do")
|
71
|
+
begin
|
72
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
73
|
+
request = Net::HTTP::Get.new(uri)
|
74
|
+
request.add_field("Cookie", "#{session[:cookie]}")
|
75
|
+
request = http.request(request)
|
76
|
+
# ...
|
77
|
+
# <a style="display:inline" href="\"javascript:" prelogout('null')\"="">Log out</a>
|
78
|
+
# ...
|
79
|
+
session_healthy = true if /preLogout/.match(request.body)
|
80
|
+
end
|
81
|
+
rescue *EXCEPTIONS => @last_error
|
82
|
+
end
|
83
|
+
session_healthy
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_all_requests
|
87
|
+
requests = Array.new
|
88
|
+
select_all_requests
|
89
|
+
puts "Getting total #{@curobj['_TL']} requests:"
|
90
|
+
get_requests_urls(@current_body).each { |url| requests.push(Request.new({ session: @session, url: url })) }
|
91
|
+
begin
|
92
|
+
not_last_page = next_page
|
93
|
+
get_requests_urls(@current_body).each { |url| requests.push(Request.new({ session: @session, url: url })) }
|
94
|
+
end while not_last_page
|
95
|
+
requests
|
96
|
+
end
|
97
|
+
|
98
|
+
def select_all_requests
|
99
|
+
session = self.session
|
100
|
+
return false unless session
|
101
|
+
uri = URI("http://#{session[:host]}:#{session[:port]}/WOListView.do")
|
102
|
+
begin
|
103
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
104
|
+
data = "globalViewName=All_Requests&viewName=All_Requests"
|
105
|
+
headers = {
|
106
|
+
"Referer" => "http://#{session[:host]}:#{session[:port]}/WOListView.do",
|
107
|
+
"Host" => "#{session[:host]}:#{session[:port]}",
|
108
|
+
"Cookie" => "#{session[:cookie]}",
|
109
|
+
}
|
110
|
+
request = http.post(uri, data, headers)
|
111
|
+
@current_body = request.response.body
|
112
|
+
@curobj = get_curobj
|
113
|
+
end
|
114
|
+
rescue *EXCEPTIONS => @last_error
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def next_page
|
119
|
+
require "date"
|
120
|
+
session = self.session
|
121
|
+
return false unless session
|
122
|
+
# 13 digits time
|
123
|
+
timestamp = DateTime.now.strftime("%Q")
|
124
|
+
uri = URI("http://#{session[:host]}:#{session[:port]}/STATE_ID/#{timestamp}/"\
|
125
|
+
"RequestsView.cc?UNIQUE_ID=RequestsView&SUBREQUEST=true")
|
126
|
+
begin
|
127
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
128
|
+
request = Net::HTTP::Get.new(uri)
|
129
|
+
request.add_field("Referer", "http://#{session[:host]}:#{session[:port]}/WOListView.do")
|
130
|
+
@curobj = get_curobj
|
131
|
+
return false unless @curobj
|
132
|
+
print "#{(@curobj["_FI"].to_f / @curobj["_TL"].to_f * 100).round}%.."
|
133
|
+
# increment page number
|
134
|
+
@curobj["_PN"] = (@curobj["_PN"].to_i + 1).to_s
|
135
|
+
# update first item
|
136
|
+
@curobj["_FI"] = (@curobj["_FI"].to_i + @curobj["_PL"].to_i).to_s
|
137
|
+
# @curobj.flatten.join("/") =>
|
138
|
+
# "_PN/2/_PL/25/_TL/28/globalViewName/All_Requests/_TI/25/_FI/1/_SO/D/viewName/All_Requests"
|
139
|
+
request.add_field("Cookie",
|
140
|
+
"#{session[:cookie]}; "\
|
141
|
+
"STATE_COOKIE=%26RequestsView/ID/#{@curobj['ID']}/VGT/#{timestamp}/#{@curobj.flatten.join('/')}"\
|
142
|
+
"/_VMD/1/ORIGROOT/#{@curobj['ID']}%26_REQS/_RVID/RequestsView/_TIME/#{timestamp}; "\
|
143
|
+
"301RequestsshowThreadedReq=showThreadedReqshow; "\
|
144
|
+
"301RequestshideThreadedReq=hideThreadedReqhide"\
|
145
|
+
""
|
146
|
+
)
|
147
|
+
request = http.request(request)
|
148
|
+
@current_body = request.response.body
|
149
|
+
end
|
150
|
+
rescue *EXCEPTIONS => @last_error
|
151
|
+
end
|
152
|
+
# if (first item + per page) > total items then it is the last page
|
153
|
+
if (@curobj["_FI"].to_i + @curobj["_PL"].to_i) > @curobj["_TL"].to_i
|
154
|
+
puts "100%"
|
155
|
+
return false
|
156
|
+
end
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_curobj
|
161
|
+
body = @current_body
|
162
|
+
# somewhere in body
|
163
|
+
# "<Script>curObj=V33;curObj[\"_PN\"]=\"1\";curObj[\"_PL\"]=\"25\";curObj[\"_TL\"]=\"28\";"\
|
164
|
+
# "curObj[\"globalViewName\"]=\"All_Requests\";curObj[\"_TI\"]=\"25\";curObj[\"_FI\"]=\"1\";"\
|
165
|
+
# "curObj[\"_SO\"]=\"D\";curObj[\"viewName\"]=\"All_Requests\";</Script>"
|
166
|
+
search_start_str = "<Script>curObj=V"
|
167
|
+
curobj_start_pos = body.index(search_start_str)
|
168
|
+
return false unless curobj_start_pos
|
169
|
+
v = /<Script>curObj=V(?<V>\d+);/.match(body)["V"]
|
170
|
+
curobj_end_pos = body.index("</Script>", curobj_start_pos)
|
171
|
+
curobj_raw = body[curobj_start_pos + search_start_str.size + v.size...curobj_end_pos]
|
172
|
+
# curobj_raw =>
|
173
|
+
# "curObj[\"_PN\"]=\"1\";curObj[\"_PL\"]=\"25\";curObj[\"_TL\"]=\"28\";"\
|
174
|
+
# "curObj[\"globalViewName\"]=\"All_Requests\";curObj[\"_TI\"]=\"25\";curObj[\"_FI\"]=\"1\";"\
|
175
|
+
# "curObj[\"_SO\"]=\"D\";curObj[\"viewName\"]=\"All_Requests\";"
|
176
|
+
curObj = Hash.new
|
177
|
+
curobj_raw.split(";").each { |c| eval(c) }
|
178
|
+
curObj["ID"] = v
|
179
|
+
# curObj =>
|
180
|
+
# {"_PN"=>"1", "_PL"=>"25", "_TL"=>"28", "globalViewName"=>"All_Requests",
|
181
|
+
# "_TI"=>"25", "_FI"=>"1", "_SO"=>"D", "viewName"=>"All_Requests"}
|
182
|
+
curObj
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_requests_urls(body)
|
186
|
+
urls = body.scan(/href=\"WorkOrder\.do\?woMode=viewWO&woID=\d+&&fromListView=true\"/)
|
187
|
+
# drop href=" and ending quot
|
188
|
+
urls.each_with_index { |url, i| urls[i] = url["href=\"".size..-2] }
|
189
|
+
end
|
190
|
+
|
191
|
+
private :select_all_requests, :next_page, :get_curobj, :get_requests_urls
|
192
|
+
end
|
193
|
+
|
194
|
+
class Request < MESD
|
195
|
+
props = [:name, :author_name, :status, :priority, :create_date, :description, :resolution]
|
196
|
+
attr_accessor :id, *props
|
197
|
+
|
198
|
+
# shortens "request.data(:resolution).resolution" to "request.get_resolution"
|
199
|
+
props.each do |prop|
|
200
|
+
define_method("get_#{prop.to_s}") do
|
201
|
+
request = self.data(prop)
|
202
|
+
request.send("#{prop.to_s}")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def initialize(args)
|
207
|
+
if args[:id]
|
208
|
+
@id = args[:id]
|
209
|
+
elsif args[:url]
|
210
|
+
if args[:url] =~ /WorkOrder\.do\?woMode=viewWO&woID=(?<ID>\d+)&&fromListView=true/
|
211
|
+
@id = Regexp.last_match("ID").to_i
|
212
|
+
else
|
213
|
+
return false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
@session = args[:session]
|
217
|
+
true
|
218
|
+
end
|
219
|
+
|
220
|
+
def data(*args)
|
221
|
+
return false unless self.id
|
222
|
+
if args.size == 0
|
223
|
+
only = []
|
224
|
+
else
|
225
|
+
only = args
|
226
|
+
end
|
227
|
+
unless session_healthy?(@session)
|
228
|
+
@last_error = "session error"
|
229
|
+
return false
|
230
|
+
end
|
231
|
+
props = [
|
232
|
+
{
|
233
|
+
name: :description,
|
234
|
+
url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
|
235
|
+
search_function: {
|
236
|
+
name: "value_between",
|
237
|
+
args: ["<td style=\"padding-left:10px;\" colspan=\"3\" valign=\"top\" class=\"fontBlack textareadesc\">", "</td>"],
|
238
|
+
},
|
239
|
+
post_processing_functions: [:strip],
|
240
|
+
},
|
241
|
+
{
|
242
|
+
name: :resolution,
|
243
|
+
url: "AddResolution.do?mode=viewWOResolution&woID=#{self.id}",
|
244
|
+
search_function: {
|
245
|
+
name: "value_between",
|
246
|
+
args: ["<td colspan=\"3\" valign=\"top\" class=\"fontBlack textareadesc\">", "</td>"],
|
247
|
+
},
|
248
|
+
post_processing_functions: [:strip],
|
249
|
+
},
|
250
|
+
{
|
251
|
+
name: :status,
|
252
|
+
url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
|
253
|
+
search_function: {
|
254
|
+
name: "html_parse",
|
255
|
+
args: [["css", "#WOHeaderSummary_DIV"], ["css", "#status_PH"], "text"],
|
256
|
+
},
|
257
|
+
post_processing_functions: [:semicolon_space_value, :symbolize],
|
258
|
+
},
|
259
|
+
{
|
260
|
+
name: :priority,
|
261
|
+
url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
|
262
|
+
search_function: {
|
263
|
+
name: "html_parse",
|
264
|
+
args: [["css", "#WOHeaderSummary_DIV"], ["css", "#priority_PH"], "text"],
|
265
|
+
},
|
266
|
+
post_processing_functions: [:semicolon_space_value, :symbolize],
|
267
|
+
},
|
268
|
+
{
|
269
|
+
name: :author_name,
|
270
|
+
url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
|
271
|
+
search_function: {
|
272
|
+
name: "html_parse",
|
273
|
+
args: [["css", "#requesterName_PH"], "text"],
|
274
|
+
},
|
275
|
+
},
|
276
|
+
{
|
277
|
+
name: :create_date,
|
278
|
+
url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
|
279
|
+
search_function: {
|
280
|
+
name: "html_parse",
|
281
|
+
args: [["css", "#CREATEDTIME_CUR"], "text"],
|
282
|
+
},
|
283
|
+
post_processing_functions: [:parse_date],
|
284
|
+
},
|
285
|
+
{
|
286
|
+
name: :name,
|
287
|
+
url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
|
288
|
+
search_function: {
|
289
|
+
name: "html_parse",
|
290
|
+
args: [["css", "#requestSubject_ID"], "text"],
|
291
|
+
},
|
292
|
+
post_processing_functions: [:strip],
|
293
|
+
},
|
294
|
+
]
|
295
|
+
props.each do |property|
|
296
|
+
next if !only.empty? && !only.include?(property[:name])
|
297
|
+
uri = URI("http://#{@session[:host]}:#{@session[:port]}/#{property[:url]}")
|
298
|
+
begin
|
299
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
300
|
+
http_request = Net::HTTP::Get.new(uri)
|
301
|
+
http_request.add_field("Cookie", "#{@session[:cookie]}")
|
302
|
+
http_request = http.request(http_request)
|
303
|
+
@current_body = http_request.response.body
|
304
|
+
auth_error_pos = @current_body.index("AuthError")
|
305
|
+
if auth_error_pos
|
306
|
+
@last_error = "auth error"
|
307
|
+
return false
|
308
|
+
end
|
309
|
+
permitions_error_pos = @current_body.index("Request does not fall under your permitted scope")
|
310
|
+
if permitions_error_pos
|
311
|
+
@last_error = "no permitions error"
|
312
|
+
return false
|
313
|
+
end
|
314
|
+
operational_error_pos = @current_body.index("failurebox")
|
315
|
+
if operational_error_pos
|
316
|
+
@last_error = "operational error"
|
317
|
+
return false
|
318
|
+
end
|
319
|
+
value = self.method(property[:search_function][:name]).call(property[:search_function][:args])
|
320
|
+
if property[:post_processing_functions]
|
321
|
+
functions = property[:post_processing_functions]
|
322
|
+
functions.each do |function|
|
323
|
+
if value.methods.include?(function)
|
324
|
+
value = value.method(function).call
|
325
|
+
elsif self.private_methods.include?(function)
|
326
|
+
value = self.method(function).call(value)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
self.send("#{property[:name]}=", value)
|
331
|
+
end
|
332
|
+
rescue *EXCEPTIONS => @last_error
|
333
|
+
end
|
334
|
+
end
|
335
|
+
self
|
336
|
+
end
|
337
|
+
|
338
|
+
def html_parse(steps)
|
339
|
+
require "nokogiri"
|
340
|
+
value = Nokogiri::HTML(@current_body)
|
341
|
+
Array(steps).each { |step| value = value.send(*step) }
|
342
|
+
value
|
343
|
+
end
|
344
|
+
|
345
|
+
def value_between(bounds)
|
346
|
+
search_start_pos = @current_body.index(bounds[0])
|
347
|
+
return "" unless search_start_pos
|
348
|
+
search_end_pos = @current_body.index(bounds[1], search_start_pos)
|
349
|
+
@current_body[search_start_pos + bounds[0].size..search_end_pos-1].force_encoding("UTF-8")
|
350
|
+
end
|
351
|
+
|
352
|
+
def semicolon_space_value(value)
|
353
|
+
value.strip[/:(.*)/m, 1].strip
|
354
|
+
end
|
355
|
+
|
356
|
+
def parse_date(date)
|
357
|
+
require "date"
|
358
|
+
DateTime.parse(date)
|
359
|
+
end
|
360
|
+
|
361
|
+
def symbolize(value)
|
362
|
+
matching = {
|
363
|
+
# status
|
364
|
+
:open => ["Открыта", "Open"],
|
365
|
+
:on_hold => ["Ожидание", "On Hold"],
|
366
|
+
:resolved => ["Решена", "Resolved"],
|
367
|
+
:closed => ["Закрыта", "Closed"],
|
368
|
+
:rejected => ["Отклонена", ""],
|
369
|
+
# priority
|
370
|
+
:minimal => ["Минимальный", ""],
|
371
|
+
:low => ["Низкий", "Low"],
|
372
|
+
:normal => ["", "Normal"],
|
373
|
+
:medium => ["Средний", "Medium"],
|
374
|
+
:high => ["Высокий", "High"],
|
375
|
+
:highest => ["Наивысший", ""],
|
376
|
+
}
|
377
|
+
matching.each do |result, candidates|
|
378
|
+
return result if candidates.include?(value)
|
379
|
+
end
|
380
|
+
value.to_sym
|
381
|
+
end
|
382
|
+
|
383
|
+
private :html_parse, :value_between, :semicolon_space_value, :parse_date, :symbolize
|
384
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: me_sd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3beta
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexander Morozov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
description: Introduces 'MESD' class that works with ManageEngine ServiceDesk Plus
|
28
|
+
without API access.
|
29
|
+
email: ntcomp12@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/me_sd.rb
|
35
|
+
homepage: https://github.com/kengho/me_sd
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.3.1
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 2.5.1
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: ManageEngine ServiceDesk Plus gem
|
59
|
+
test_files: []
|