me_sd 0.0.3beta
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.
- 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: []
|