rt-client 0.6.8 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/rt/client.rb CHANGED
@@ -1,822 +1 @@
1
- #!/usr/bin/ruby
2
-
3
- require "rubygems"
4
- require "rest_client"
5
-
6
- ## Author:: Tom Lahti
7
- ## Copyright:: Copyright (c) 2013 Tom Lahti
8
- ## License:: Apache 2.0
9
-
10
- #{<img src="https://badge.fury.io/rb/rt-client.png" alt="Gem Version" />}[http://badge.fury.io/rb/rt-client]
11
- ##A ruby library API to Request Tracker's REST interface. Requires the
12
- ##rubygem rest-client be installed. You can
13
- ##create a file name .rtclientrc in the same directory as client.rb with a
14
- ##default server/user/pass to connect to RT as, so that you don't have to
15
- ##specify it/update it in lots of different scripts.
16
- ##
17
- ##Thanks to Brian McArdle for patch dealing with spaces in Custom Fields.
18
- ##To reference custom fields in RT that have spaces with rt-client, use an
19
- ##underscore in the rt-client code, e.g. "CF.{{Has_Space}}"
20
- ##
21
- ##Thanks to Robert Vinson for 1.9.x compatibility when I couldn't be buggered
22
- ##and a great job of refactoring the old mess into something with much fewer
23
- ##dependencies.
24
- ##
25
- ##TODO: Streaming, chunking attachments in compose method
26
- #
27
- # See each method for sample usage. To use this, "gem install rt-client" and
28
- #
29
- # require "rt/client"
30
-
31
- class RT_Client
32
-
33
- UA = "Mozilla/5.0 ruby RT Client Interface 0.6.8"
34
- attr_reader :status, :site, :version, :cookies, :server, :user, :cookie
35
-
36
- # Create a new RT_Client object. Load up our stored cookie and check it.
37
- # Log into RT again if needed and store the new cookie. You can specify
38
- # login and cookie storage directories in 3 different ways:
39
- # 1. Explicity during object creation
40
- # 2. From a .rtclientrc file in the working directory of your ruby program
41
- # 3. From a .rtclientrc file in the same directory as the library itself
42
- #
43
- # These are listed in order of priority; if you have explicit parameters,
44
- # they are always used, even if you have .rtclientrc files. If there
45
- # is both an .rtclientrc in your program's working directory and
46
- # in the library directory, the one from your program's working directory
47
- # is used. If no parameters are specified either explicity or by use
48
- # of a .rtclientrc, then the defaults of "rt_user", "rt_pass" are used
49
- # with a default server of "http://localhost", and cookies are stored
50
- # in the directory where the library resides.
51
- #
52
- # rt= RT_Client.new( :server => "https://tickets.ambulance.com/",
53
- # :user => "rt_user",
54
- # :pass => "rt_pass",
55
- # :cookies => "/my/cookie/dir" )
56
- #
57
- # rt= RT_Client.new # use defaults from .rtclientrc
58
- #
59
- # .rtclientrc format:
60
- # server=<RT server>
61
- # user=<RT user>
62
- # pass=<RT password>
63
- # cookies=<directory>
64
- def initialize(*params)
65
- @boundary = "----xYzZY#{rand(1000000).to_s}xYzZY"
66
- @version = "0.6.8"
67
- @status = "Not connected"
68
- @server = "http://localhost/"
69
- @user = "rt_user"
70
- @pass = "rt_pass"
71
- @cookies = Dir.pwd
72
- config_file = Dir.pwd + "/.rtclientrc"
73
- config = ""
74
- if File.file?(config_file)
75
- config = File.read(config_file)
76
- else
77
- config_file = File.dirname(__FILE__) + "/.rtclientrc"
78
- config = File.read(config_file) if File.file?(config_file)
79
- end
80
- @server = $~[1] if config =~ /\s*server\s*=\s*(.*)$/i
81
- @user = $~[1] if config =~ /^\s*user\s*=\s*(.*)$/i
82
- @pass = $~[1] if config =~ /^\s*pass\s*=\s*(.*)$/i
83
- @cookies = $~[1] if config =~ /\s*cookies\s*=\s*(.*)$/i
84
- @resource = "#{@server}REST/1.0/"
85
- if params.class == Array && params[0].class == Hash
86
- param = params[0]
87
- @user = param[:user] if param.has_key? :user
88
- @pass = param[:pass] if param.has_key? :pass
89
- if param.has_key? :server
90
- @server = param[:server]
91
- @server += "/" if @server !~ /\/$/
92
- @resource = "#{@server}REST/1.0/"
93
- end
94
- @cookies = param[:cookies] if param.has_key? :cookies
95
- end
96
- @login = { :user => @user, :pass => @pass }
97
- cookiejar = "#{@cookies}/RT_Client.#{@user}.cookie" # cookie location
98
- cookiejar.untaint
99
- if File.file? cookiejar
100
- @cookie = File.read(cookiejar).chomp
101
- headers = { 'User-Agent' => UA,
102
- 'Content-Type' => "application/x-www-form-urlencoded",
103
- 'Cookie' => @cookie }
104
- else
105
- headers = { 'User-Agent' => UA,
106
- 'Content-Type' => "application/x-www-form-urlencoded" }
107
- @cookie = ""
108
- end
109
-
110
-
111
- site = RestClient::Resource.new(@resource, :headers => headers, :timeout => 240)
112
- data = site.post "" # a null post just to check that we are logged in
113
-
114
- if @cookie.length == 0 or data =~ /401/ # we're not logged in
115
- data = site.post @login, :headers => headers
116
- # puts data
117
- @cookie = data.headers[:set_cookie].first.split('; ')[0]
118
- # write the new cookie
119
- if @cookie !~ /nil/
120
- f = File.new(cookiejar,"w")
121
- f.puts @cookie
122
- f.close
123
- end
124
- end
125
- headers = { 'User-Agent' => UA,
126
- 'Content-Type' => "multipart/form-data; boundary=#{@boundary}",
127
- 'Cookie' => @cookie }
128
- @site = RestClient::Resource.new(@resource, :headers => headers)
129
- @status = data
130
- self.untaint
131
- end
132
-
133
- # gets the detail for a single ticket/user. If its a ticket, its without
134
- # history or attachments (to get those use the history method) . If no
135
- # type is specified, ticket is assumed. takes a single parameter
136
- # containing the ticket/user id, and returns a hash of RT Fields => values
137
- #
138
- # hash = rt.show(822)
139
- # hash = rt.show("822")
140
- # hash = rt.show("ticket/822")
141
- # hash = rt.show(:id => 822)
142
- # hash = rt.show(:id => "822")
143
- # hash = rt.show(:id => "ticket/822")
144
- # hash = rt.show("user/#{login}")
145
- # email = rt.show("user/somebody")["emailaddress"]
146
- def show(id)
147
- id = id[:id] if id.class == Hash
148
- id = id.to_s
149
- type = "ticket"
150
- sid = id
151
- if id =~ /(\w+)\/(.+)/
152
- type = $~[1]
153
- sid = $~[2]
154
- end
155
- reply = {}
156
- if type.downcase == 'user'
157
- resp = @site["#{type}/#{sid}"].get
158
- else
159
- resp = @site["#{type}/#{sid}/show"].get
160
- end
161
- reply = response_to_h(resp)
162
- end
163
-
164
- # gets a list of ticket links for a ticket.
165
- # takes a single parameter containing the ticket id,
166
- # and returns a hash of RT Fields => values
167
- #
168
- # hash = rt.links(822)
169
- # hash = rt.links("822")
170
- # hash = rt.links("ticket/822")
171
- # hash = rt.links(:id => 822)
172
- # hash = rt.links(:id => "822")
173
- # hash = rt.links(:id => "ticket/822")
174
- def links(id)
175
- id = id[:id] if id.class == Hash
176
- id = id.to_s
177
- type = "ticket"
178
- sid = id
179
- if id =~ /(\w+)\/(.+)/
180
- type = $~[1]
181
- sid = $~[2]
182
- end
183
- reply = {}
184
- resp = @site["ticket/#{sid}/links/show"].get
185
- return {:error => resp, } if resp =~ /does not exist./
186
- reply = response_to_h(resp)
187
- end
188
-
189
- # Creates a new ticket. Requires a hash that contains RT form fields as
190
- # the keys. Capitalization is important; use :Queue, not :queue. You
191
- # will need at least :Queue to create a ticket. For a full list of fields
192
- # you can use, try "/opt/rt3/bin/rt edit ticket/1". Returns the newly
193
- # created ticket number, or a complete REST response.
194
- #
195
- # id = rt.create( :Queue => "Customer Service",
196
- # :Cc => "somebody\@email.com",
197
- # :Subject => "I've fallen and I can't get up",
198
- # :Text => "I think my hip is broken.\nPlease help.",
199
- # :"CF.{CustomField}" => "Urgent",
200
- def create(field_hash)
201
- field_hash[:id] = "ticket/new"
202
- payload = compose(field_hash)
203
- # puts "Payload for new ticket:"
204
- # puts payload
205
- resp = @site['ticket/new/edit'].post payload
206
- new_id = resp.match(/Ticket\s*(\d+)/)
207
- if new_id.class == MatchData
208
- new_ticket = new_id[1]
209
- else
210
- new_ticket = resp
211
- end
212
- new_ticket # return the ticket number, or the full REST response
213
- end
214
-
215
- # create a new user. Requires a hash of RT fields => values. Returns
216
- # the newly created user ID, or the full REST response if there is an error.
217
- # For a full list of possible parameters that you can specify, look at
218
- # "/opt/rt/bin/rt edit user/1"
219
- #
220
- # new_id = rt.create_user(:Name => "Joe_Smith", :EmailAddress => "joes\@here.com")
221
- def create_user(field_hash)
222
- field_hash[:id] = "user/new"
223
- payload = compose(field_hash)
224
- resp = @site['user/new/edit'].post payload
225
- new_id = resp.match(/User\s*(\d+)/)
226
- if new_id.class == MatchData
227
- new_user = new_id[1]
228
- else
229
- new_user = resp
230
- end
231
- new_user # return the new user id or the full REST response
232
- end
233
-
234
- # edit or create a user. If the user exists, edits the user as "edit" would.
235
- # If the user doesn't exist, creates it as "create_user" would.
236
- def edit_or_create_user(field_hash)
237
- if field_hash.has_key? :id
238
- id = field_hash[:id]
239
- if id !~ /^user\//
240
- id = "user/#{id}"
241
- field_hash[:id] = id
242
- end
243
- else
244
- raise "RT_Client.edit_or_create_user require a user id in the 'id' key."
245
- end
246
- resp1 = "not called"
247
- resp2 = "not called"
248
- resp1 = edit(field_hash)
249
- resp2 = create_user(field_hash) if resp1 =~ /does not exist./
250
- resp = "Edit: #{resp1}\nCreate:#{resp2}"
251
- resp
252
- end
253
-
254
- # edit an existing ticket/user. Requires a hash containing RT
255
- # form fields as keys. the key :id is required.
256
- # returns the complete REST response, whatever it is. If the
257
- # id supplied contains "user/", it edits a user, otherwise
258
- # it edits a ticket. For a full list of fields you can edit,
259
- # try "/opt/rt3/bin/rt edit ticket/1"
260
- #
261
- # resp = rt.edit(:id => 822, :Status => "resolved")
262
- # resp = rt.edit(:id => ticket_id, :"CF.{CustomField}" => var)
263
- # resp = rt.edit(:id => "user/someone", :EMailAddress => "something@here.com")
264
- # resp = rt.edit(:id => "user/bossman", :Password => "mypass")
265
- # resp = rt.edit(:id => "user/4306", :Disabled => "1")
266
- def edit(field_hash)
267
- if field_hash.has_key? :id
268
- id = field_hash[:id]
269
- else
270
- raise "RT_Client.edit requires a user or ticket id in the 'id' key."
271
- end
272
- type = "ticket"
273
- sid = id
274
- if id =~ /(\w+)\/(.+)/
275
- type = $~[1]
276
- sid = $~[2]
277
- end
278
- payload = compose(field_hash)
279
- resp = @site["#{type}/#{sid}/edit"].post payload
280
- resp
281
- end
282
-
283
- # Comment on a ticket. Requires a hash, which must have an :id key
284
- # containing the ticket number. Returns the REST response. For a list of
285
- # fields you can use in a comment, try "/opt/rt3/bin/rt comment ticket/1"
286
- #
287
- # rt.comment( :id => id,
288
- # :Text => "Oh dear, I wonder if the hip smells like almonds?",
289
- # :Attachment => "/tmp/almonds.gif" )
290
- def comment(field_hash)
291
- if field_hash.has_key? :id
292
- id = field_hash[:id]
293
- else
294
- raise "RT_Client.comment requires a Ticket number in the 'id' key."
295
- end
296
- field_hash[:Action] = "comment"
297
- payload = compose(field_hash)
298
- @site["ticket/#{id}/comment"].post payload
299
- end
300
-
301
- # Find RT user details from an email address
302
- #
303
- # rt.usersearch(:EmailAddress => 'some@email.com')
304
- # -> {"name"=>"rtlogin", "realname"=>"John Smith", "address1"=>"123 Main", etc }
305
- def usersearch(field_hash)
306
- if field_hash.has_key? :EmailAddress
307
- email = field_hash[:EmailAddress]
308
- else
309
- raise "RT_Client.usersearch requires a user email in the 'EmailAddress' key."
310
- end
311
- resp = @site["user/#{email}"].get
312
- return reply if resp =~ /No user named/
313
- reply = response_to_h(resp)
314
- end
315
-
316
- # correspond on a ticket. Requires a hash, which must have an :id key
317
- # containing the ticket number. Returns the REST response. For a list of
318
- # fields you can use in correspondence, try "/opt/rt3/bin/rt correspond
319
- # ticket/1"
320
- #
321
- # rt.correspond( :id => id,
322
- # :Text => "We're sending help right away.",
323
- # :Attachment => "/tmp/admittance.doc" )
324
- def correspond(field_hash)
325
- if field_hash.has_key? :id
326
- if field_hash[:id] =~ /ticket\/(\d+)/
327
- id = $~[1]
328
- else
329
- id = field_hash[:id]
330
- end
331
- else
332
- raise "RT_Client.correspond requires a Ticket number in the 'id' key."
333
- end
334
- field_hash[:Action] = "correspond"
335
- payload = compose(field_hash)
336
- @site["ticket/#{id}/comment"].post payload
337
- end
338
-
339
- # Get a list of tickets matching some criteria.
340
- # Takes a string Ticket-SQL query and an optional "order by" parameter.
341
- # The order by is an RT field, prefix it with + for ascending
342
- # or - for descending.
343
- # Returns a nested array of arrays containing [ticket number, subject]
344
- # The outer array is in the order requested.
345
- #
346
- # hash = rt.list(:query => "Queue = 'Sales'")
347
- # hash = rt.list("Queue='Sales'")
348
- # hash = rt.list(:query => "Queue = 'Sales'", :order => "-Id")
349
- # hash = rt.list("Queue='Sales'","-Id")
350
- def list(*params)
351
- query = params[0]
352
- order = ""
353
- if params.size > 1
354
- order = params[1]
355
- end
356
- if params[0].class == Hash
357
- params = params[0]
358
- query = params[:query] if params.has_key? :query
359
- order = params[:order] if params.has_key? :order
360
- end
361
- reply = []
362
- resp = @site["search/ticket/?query=#{URI.escape(query)}&orderby=#{order}&format=s"].get
363
- raise "Invalid query (#{query})" if resp =~ /Invalid query/
364
- resp = resp.split("\n") # convert to array of lines
365
- resp.each do |line|
366
- f = line.match(/^(\d+):\s*(.*)/)
367
- reply.push [f[1],f[2]] if f.class == MatchData
368
- end
369
- reply
370
- end
371
-
372
- # A more extensive(expensive) query then the list method. Takes the same
373
- # parameters as the list method; a string Ticket-SQL query and optional
374
- # order, but returns a lot more information. Instead of just the ID and
375
- # subject, you get back an array of hashes, where each hash represents
376
- # one ticket, indentical to what you get from the show method (which only
377
- # acts on one ticket). Use with caution; this can take a long time to
378
- # execute.
379
- #
380
- # array = rt.query("Queue='Sales'")
381
- # array = rt.query(:query => "Queue='Sales'",:order => "+Id")
382
- # array = rt.query("Queue='Sales'","+Id")
383
- # => array[0] = { "id" => "123", "requestors" => "someone@..", etc etc }
384
- # => array[1] = { "id" => "126", "requestors" => "someone@else..", etc etc }
385
- # => array[0]["id"] = "123"
386
- def query(*params)
387
- query = params[0]
388
- order = ""
389
- if params.size > 1
390
- order = params[1]
391
- end
392
- if params[0].class == Hash
393
- params = params[0]
394
- query = params[:query] if params.has_key? :query
395
- order = params[:order] if params.has_key? :order
396
- end
397
- replies = []
398
- resp = @site["search/ticket/?query=#{URI.escape(query)}&orderby=#{order}&format=l"].get
399
- return replies if resp =~/No matching results./
400
- raise "Invalid query (#{query})" if resp =~ /Invalid query/
401
- resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # strip HTTP response
402
- tickets = resp.split("\n--\n") # -- occurs between each ticket
403
- tickets.each do |ticket|
404
- ticket_h = response_to_h(ticket)
405
- reply = {}
406
- ticket_h.each do |k,v|
407
- case k
408
- when 'created','due','told','lastupdated','started'
409
- begin
410
- vv = DateTime.parse(v.to_s)
411
- reply["#{k}"] = vv.strftime("%Y-%m-%d %H:%M:%S")
412
- rescue ArgumentError
413
- reply["#{k}"] = v.to_s
414
- end
415
- else
416
- reply["#{k}"] = v.to_s
417
- end
418
- end
419
- replies.push reply
420
- end
421
- replies
422
- end
423
-
424
- # Get a list of history transactions for a ticket. Takes a ticket ID and
425
- # an optional format parameter. If the format is ommitted, the short
426
- # format is assumed. If the short format is requested, it returns an
427
- # array of 2 element arrays, where each 2-element array is [ticket_id,
428
- # description]. If the long format is requested, it returns an array of
429
- # hashes, where each hash contains the keys:
430
- #
431
- # id:: (history-id)
432
- # Ticket:: (Ticket this history item belongs to)
433
- # TimeTaken:: (time entered by the actor that generated this item)
434
- # Type:: (what type of history item this is)
435
- # Field:: (what field is affected by this item, if any)
436
- # OldValue:: (the old value of the Field)
437
- # NewValue:: (the new value of the Field)
438
- # Data:: (Additional data about the item)
439
- # Description:: (Description of this item; same as short format)
440
- # Content:: (The content of this item)
441
- # Creator:: (the RT user that created this item)
442
- # Created:: (Date/time this item was created)
443
- # Attachments:: (a hash describing attachments to this item)
444
- #
445
- # history = rt.history(881)
446
- # history = rt.history(881,"short")
447
- # => [["10501"," Ticket created by blah"],["10510"," Comments added by userX"]]
448
- # history = rt.history(881,"long")
449
- # history = rt.history(:id => 881, :format => "long")
450
- # => [{"id" => "6171", "ticket" => "881" ....}, {"id" => "6180", ...} ]
451
- def history(*params)
452
- id = params[0]
453
- format = "short"
454
- format = params[1].downcase if params.size > 1
455
- comments = false
456
- comments = params[2] if params.size > 2
457
- if params[0].class == Hash
458
- params = params[0]
459
- id = params[:id] if params.has_key? :id
460
- format = params[:format].downcase if params.has_key? :format
461
- comments = params[:comments] if params.has_key? :comments
462
- end
463
- id = id.to_s
464
- id = $~[1] if id =~ /ticket\/(\d+)/
465
- resp = @site["ticket/#{id}/history?format=#{format[0,1]}"].get
466
- resp.gsub!(0x1C.chr,'[FS]')
467
- resp.gsub!(0x1D.chr,'[GS]')
468
- resp.gsub!(0x1E.chr,'[RS]')
469
- resp.gsub!(0x1F.chr,'[US]')
470
-
471
- if format[0,1] == "s"
472
- if comments
473
- h = resp.split("\n").select{ |l| l =~ /^\d+:/ }
474
- else
475
- h = resp.split("\n").select{ |l| l =~ /^\d+: [^Comments]/ }
476
- h.select!{ |l| l !~ /Outgoing email about a comment/ }
477
- end
478
- list = h.map { |l| l.split(":") }
479
- else
480
- resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
481
- resp.gsub!(/^#.*?\n\n/,"") # toss the 'total" line
482
- resp.gsub!(/^\n/m,"") # toss blank lines
483
- while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
484
- resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
485
- end
486
-
487
- items = resp.split("\n--\n")
488
- list = []
489
- items.each do |item|
490
- th = response_to_h(item)
491
- next if not comments and th["type"].to_s =~ /Comment/ # skip comments
492
- reply = {}
493
- th.each do |k,v|
494
- attachments = []
495
- case k
496
- when "attachments"
497
- temp = item.match(/Attachments:\s*(.*)/m)
498
- if temp.class != NilClass
499
- atarr = temp[1].split("\n")
500
- atarr.map { |a| a.gsub!(/^\s*/,"") }
501
- atarr.each do |a|
502
- i = a.match(/(\d+):\s*(.*)/)
503
- s={}
504
- s[:id] = i[1].to_s
505
- s[:name] = i[2].to_s
506
- sz = i[2].match(/(.*?)\s*\((.*?)\)/)
507
- if sz.class == MatchData
508
- s[:name] = sz[1].to_s
509
- s[:size] = sz[2].to_s
510
- end
511
- attachments.push s
512
- end
513
- reply["attachments"] = attachments
514
- end
515
- when "content"
516
- reply["content"] = v.to_s
517
- temp = item.match(/^Content: (.*?)^\w+:/m)
518
- reply["content"] = temp[1] if temp.class != NilClass
519
- else
520
- reply["#{k}"] = v.to_s
521
- end
522
- end
523
- list.push reply
524
- end
525
- end
526
- list
527
- end
528
-
529
- # Get the detail for a single history item. Needs a ticket ID and a
530
- # history item ID, returns a hash of RT Fields => values. The hash
531
- # also contains a special key named "attachments", whose value is
532
- # an array of hashes, where each hash represents an attachment. The hash
533
- # keys are :id, :name, and :size.
534
- #
535
- # x = rt.history_item(21, 6692)
536
- # x = rt.history_item(:id => 21, :history => 6692)
537
- # => x = {"ticket" => "21", "creator" => "somebody", "description" =>
538
- # => "something happened", "attachments" => [{:name=>"file.txt",
539
- # => :id=>"3289", size=>"651b"}, {:name=>"another.doc"... }]}
540
- def history_item(*params)
541
- id = params[0]
542
- history = params[1]
543
- if params[0].class == Hash
544
- params = params[0]
545
- id = params[:id] if params.has_key? :id
546
- history = params[:history] if params.has_key? :history
547
- end
548
- reply = {}
549
- resp = @site["ticket/#{id}/history/id/#{history}"].get
550
- return reply if resp =~ /not related/ # history id must be related to the ticket id
551
- resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
552
- resp.gsub!(/^#.*?\n\n/,"") # toss the 'total" line
553
- resp.gsub!(/^\n/m,"") # toss blank lines
554
- while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
555
- resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
556
- end
557
- th = response_to_h(resp)
558
- attachments = []
559
- th.each do |k,v|
560
- case k
561
- when "attachments"
562
- temp = resp.match(/Attachments:\s*(.*)[^\w|$]/m)
563
- if temp.class != NilClass
564
- atarr = temp[1].split("\n")
565
- atarr.map { |a| a.gsub!(/^\s*/,"") }
566
- atarr.each do |a|
567
- i = a.match(/(\d+):\s*(.*)/)
568
- s={}
569
- s[:id] = i[1]
570
- s[:name] = i[2]
571
- sz = i[2].match(/(.*?)\s*\((.*?)\)/)
572
- if sz.class == MatchData
573
- s[:name] = sz[1]
574
- s[:size] = sz[2]
575
- end
576
- attachments.push s
577
- end
578
- reply["#{k}"] = attachments
579
- end
580
- when "content"
581
- reply["content"] = v.to_s
582
- temp = resp.match(/^Content: (.*?)^\w+:/m)
583
- reply["content"] = temp[1] if temp.class != NilClass
584
- else
585
- reply["#{k}"] = v.to_s
586
- end
587
- end
588
- reply
589
- end
590
-
591
- # Get a list of attachments related to a ticket.
592
- # Requires a ticket id, returns an array of hashes where each hash
593
- # represents one attachment. Hash keys are :id, :name, :type, :size.
594
- # You can optionally request that unnamed attachments be included,
595
- # the default is to not include them.
596
- def attachments(*params)
597
- id = params[0]
598
- unnamed = params[1]
599
- if params[0].class == Hash
600
- params = params[0]
601
- id = params[:id] if params.has_key? :id
602
- unnamed = params[:unnamed] if params.has_key? :unnamed
603
- end
604
- unnamed = false if unnamed.to_s == "0"
605
- id = $~[1] if id =~ /ticket\/(\d+)/
606
- resp = @site["ticket/#{id}/attachments"].get
607
- resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
608
- resp.gsub!(/^\n/m,"") # toss blank lines
609
- while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
610
- resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
611
- end
612
- th = response_to_h(resp)
613
- list = []
614
- pattern = /(\d+:\s.*?\)),/
615
- match = pattern.match(th['attachments'].to_s)
616
- while match != nil
617
- list.push match[0]
618
- s = match.post_match
619
- match = pattern.match(s)
620
- end
621
- attachments = []
622
- list.each do |v|
623
- attachment = {}
624
- m=v.match(/(\d+):\s+(.*?)\s+\((.*?)\s+\/\s+(.*?)\)/)
625
- if m.class == MatchData
626
- next if m[2] == "(Unnamed)" and !unnamed
627
- attachment[:id] = m[1]
628
- attachment[:name] = m[2]
629
- attachment[:type] = m[3]
630
- attachment[:size] = m[4]
631
- attachments.push attachment
632
- end
633
- end
634
- attachments
635
- end
636
-
637
- # Get attachment content for single attachment. Requires a ticket ID
638
- # and an attachment ID, which must be related. If a directory parameter
639
- # is supplied, the attachment is written to that directory. If not,
640
- # the attachment content is returned in the hash returned by the
641
- # function as the key 'content', along with some other keys you always get:
642
- #
643
- # transaction:: the transaction id
644
- # creator:: the user id number who attached it
645
- # id:: the attachment id
646
- # filename:: the name of the file
647
- # contenttype:: MIME content type of the attachment
648
- # created:: date of the attachment
649
- # parent:: an attachment id if this was an embedded MIME attachment
650
- #
651
- # x = get_attachment(21,3879)
652
- # x = get_attachment(:ticket => 21, :attachment => 3879)
653
- # x = get_attachment(:ticket => 21, :attachment => 3879, :dir = "/some/dir")
654
- def get_attachment(*params)
655
- tid = params[0]
656
- aid = params[1]
657
- dir = nil
658
- dir = params[2] if params.size > 2
659
- if params[0].class == Hash
660
- params = params[0]
661
- tid = params[:ticket] if params.has_key? :ticket
662
- aid = params[:attachment] if params.has_key? :attachment
663
- dir = params[:dir] if params.has_key? :dir
664
- end
665
- tid = $~[1] if tid =~ /ticket\/(\d+)/
666
- resp = @site["ticket/#{tid}/attachments/#{aid}"].get
667
- resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss HTTP response
668
- while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
669
- resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
670
- end
671
- headers = response_to_h(resp)
672
- reply = {}
673
- headers.each do |k,v|
674
- reply["#{k}"] = v.to_s
675
- end
676
- content = resp.match(/Content:\s+(.*)/m)[1]
677
- content.gsub!(/\n\s{9}/,"\n") # strip leading spaces on each line
678
- content.chomp!
679
- content.chomp!
680
- content.chomp! # 3 carriage returns at the end
681
- binary = Iconv.conv("ISO-8859-1","UTF-8",content) # convert encoding
682
- if dir
683
- fh = File.new("#{dir}/#{headers['Filename'].to_s}","wb")
684
- fh.write binary
685
- fh.close
686
- else
687
- reply["content"] = binary
688
- end
689
- reply
690
- end
691
-
692
- # Add a watcher to a ticket, but only if not already a watcher. Takes a
693
- # ticket ID, an email address (or array of email addresses), and an
694
- # optional watcher type. If no watcher type is specified, its assumed to
695
- # be "Cc". Possible watcher types are 'Requestors', 'Cc', and 'AdminCc'.
696
- #
697
- # rt.add_watcher(123,"someone@here.com")
698
- # rt.add_watcher(123,["someone@here.com","another@there.com"])
699
- # rt.add_watcher(123,"someone@here.com","Requestors")
700
- # rt.add_watcher(:id => 123, :addr => "someone@here.com")
701
- # rt.add_watcher(:id => 123, :addr => ["someone@here.com","another@there.com"])
702
- # rt.add_watcher(:id => 123, :addr => "someone@here.com", :type => "AdminCc")
703
- def add_watcher(*params)
704
- tid = params[0]
705
- addr = []
706
- type = "cc"
707
- addr = params[1] if params.size > 1
708
- type = params[2] if params.size > 2
709
- if params[0].class == Hash
710
- params = params[0]
711
- tid = params[:id] if params.has_key? :id
712
- addr = params[:addr] if params.has_key? :addr
713
- type = params[:type] if params.has_key? :type
714
- end
715
- addr = addr.to_a.uniq # make it array if its just a string, and remove dups
716
- type.downcase!
717
- tobj = show(tid) # get current watchers
718
- ccs = tobj["cc"].split(", ")
719
- accs = tobj["admincc"].split(", ")
720
- reqs = tobj["requestors"].split(", ")
721
- watchers = ccs | accs | reqs # union of all watchers
722
- addr.each do |e|
723
- case type
724
- when "cc"
725
- ccs.push(e) if not watchers.include?(e)
726
- when "admincc"
727
- accs.push(e) if not watchers.include?(e)
728
- when "requestors"
729
- reqs.push(e) if not watchers.include?(e)
730
- end
731
- end
732
- case type
733
- when "cc"
734
- edit(:id => tid, :Cc => ccs.join(","))
735
- when "admincc"
736
- edit(:id => tid, :AdminCc => accs.join(","))
737
- when "requestors"
738
- edit(:id => tid, :Requestors => reqs.join(","))
739
- end
740
- end
741
-
742
-
743
- # don't give up the password when the object is inspected
744
- def inspect # :nodoc:
745
- mystr = super()
746
- mystr.gsub!(/(.)pass=.*?([,\}])/,"\\1pass=<hidden>\\2")
747
- mystr
748
- end
749
-
750
- # helper to convert responses from RT REST to a hash
751
- def response_to_h(resp) # :nodoc:
752
- resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
753
- #resp.gsub!(/\n\n/,"\n") # remove double spacing, TMail stops at a blank line
754
-
755
- # unfold folded fields
756
- # A newline followed by one or more spaces is treated as a
757
- # single space
758
- resp.gsub!(/\n +/, " ")
759
-
760
- #replace CF spaces with underscores
761
- while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/)
762
- resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
763
- end
764
- return {:error => resp } if resp =~ /does not exist./
765
-
766
- # convert fields to key value pairs
767
- ret = {}
768
- resp.each_line do |ln|
769
- next unless ln =~ /^.+?:/
770
- ln_a = ln.split(/:/,2)
771
- ln_a.map! {|item| item.strip}
772
- ln_a[0].downcase!
773
- ret[ln_a[0]] = ln_a[1]
774
- end
775
-
776
- return ret
777
- end
778
-
779
- # Helper for composing RT's "forms". Requires a hash where the
780
- # keys are field names for an RT form. If there's a :Text key, the value
781
- # is modified to insert whitespace on continuation lines. If there's an
782
- # :Attachment key, the value is assumed to be a comma-separated list of
783
- # filenames to attach. It returns a hash to be used with rest-client's
784
- # payload class
785
- def compose(fields) # :nodoc:
786
- if fields.class != Hash
787
- raise "RT_Client.compose requires parameters as a hash."
788
- end
789
-
790
- payload = { :multipart => true }
791
-
792
- # attachments
793
- if fields.has_key? :Attachments
794
- fields[:Attachment] = fields[:Attachments]
795
- fields.delete :Attachments
796
- end
797
- if fields.has_key? :Attachment
798
- filenames = fields[:Attachment].split(',')
799
- attachment_num = 1
800
- filenames.each do |f|
801
- payload["attachment_#{attachment_num.to_s}"] = File.new(f)
802
- attachment_num += 1
803
- end
804
- # strip paths from filenames
805
- fields[:Attachment] = filenames.map {|f| File.basename(f)}.join(',')
806
- end
807
-
808
- # fixup Text field for RFC822 compliance
809
- if fields.has_key? :Text
810
- fields[:Text].gsub!(/\n/,"\n ") # insert a space on continuation lines.
811
- end
812
-
813
- field_array = fields.map { |k,v| "#{k}: #{v}" }
814
- content = field_array.join("\n") # our form
815
- payload["content"] = content
816
-
817
- return payload
818
- end
819
-
820
- end
821
-
822
-
1
+ require "rt_client"