yammdesk 0.0.1
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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +674 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/TODO +8 -0
- data/bin/SyncTicketing.rb +132 -0
- data/lib/yammdesk.rb +15 -0
- data/lib/yammdesk/version.rb +3 -0
- data/lib/yammdesk/whd.rb +158 -0
- data/lib/yammdesk/yammer.rb +97 -0
- data/templates/config.yml +10 -0
- data/templates/create_ticket.json +32 -0
- data/templates/update_ticket.json +25 -0
- data/yammdesk.gemspec +29 -0
- metadata +165 -0
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Yammdesk
|
2
|
+
|
3
|
+
|
4
|
+
## WARNING
|
5
|
+
WARNING: THIS SOFTWARE IS ALPHA. It has bugs which may set fire to your wife. If you do not have a wife, it may seek out the equivalent and set fire to that. I strongly suggest you do not use it until this notice disappears.
|
6
|
+
|
7
|
+
## Description
|
8
|
+
|
9
|
+
A two-way interface between Yammer, and your ticketing system. Currently only Web Help Desk is supported, but there are plans to include Zendesk.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
$ gem install yammdesk
|
14
|
+
|
15
|
+
Use the configuration template provided in the templates folder to create a conf file in the folder you will be running the script from.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Run SyncTicketing.rb to sync Yammer <=> Ticketing System.
|
20
|
+
|
21
|
+
Alternatively, you can use the methods provided to write your own solution.
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
Many things are broken ;)
|
4
|
+
|
5
|
+
* Ensure that tokens are not passed via URL
|
6
|
+
* Refactor API call methods into one method that can be called for all jobs.
|
7
|
+
* Ensure regular expressions that re-write the API call responses catch _all_ abnormal text (HTML) and re-write them to plain text.
|
8
|
+
* Look into using ['rich'] Yammer message response instead of ['plain'], to get around the Yammer user name change bug (which means that if a Yammer user changes their name, any messages they send will not be found when searching the ticketing system).
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yammdesk'
|
4
|
+
require 'colorize'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
beginning = Time.now
|
8
|
+
yamm = Yammdesk::Yammer.new
|
9
|
+
whd = Yammdesk::WHD.new
|
10
|
+
yamm_results = yamm.get
|
11
|
+
yamm_results.sort_by { |id| }
|
12
|
+
|
13
|
+
|
14
|
+
yamm_results.each { |m|
|
15
|
+
id = m['id']
|
16
|
+
replied_to_id = m["replied_to_id"]
|
17
|
+
thread_id = m["thread_id"]
|
18
|
+
body = m["body"]["plain"]
|
19
|
+
|
20
|
+
thread_exists = whd.thread_exists?(thread_id)
|
21
|
+
thread_starter = yamm.is_thread_starter?(id, thread_id)
|
22
|
+
|
23
|
+
# TODO: Write these methods
|
24
|
+
# ticket_closed = WHD.ticket_closed?(thread_id)
|
25
|
+
#
|
26
|
+
if thread_exists && !thread_starter
|
27
|
+
puts "Thread is not a thread starter, AND ticket exists".yellow
|
28
|
+
puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".yellow
|
29
|
+
puts "thread_id = #{thread_id}".yellow
|
30
|
+
puts "messag id = #{id}".yellow
|
31
|
+
puts "body = #{body}\n".yellow
|
32
|
+
|
33
|
+
ticket_id = whd.search(thread_id)
|
34
|
+
if !whd.note_exists?(ticket_id, body)
|
35
|
+
puts "Thread is not a thread starter, AND ticket exists, AND note does not exist".blue
|
36
|
+
puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".blue
|
37
|
+
puts "thread_id = #{thread_id}".blue
|
38
|
+
puts "messag id = #{id}".blue
|
39
|
+
puts "body = #{body}\n".blue
|
40
|
+
|
41
|
+
whd.update_ticket(ticket_id, body)
|
42
|
+
end
|
43
|
+
elsif !thread_exists && thread_starter
|
44
|
+
puts "Thread does not exist, AND is thread_starter".green
|
45
|
+
puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".green
|
46
|
+
puts "thread_id = #{thread_id}".green
|
47
|
+
puts "messag id = #{id}".green
|
48
|
+
puts "body = #{body}".green
|
49
|
+
|
50
|
+
whd.create_ticket(thread_id, body)
|
51
|
+
|
52
|
+
ticket_id = whd.search(thread_id)
|
53
|
+
thread_replies = yamm.get_thread_replies(thread_id)
|
54
|
+
thread_replies.sort_by { |id| }
|
55
|
+
thread_replies.each { |m|
|
56
|
+
id = m['id']
|
57
|
+
replied_to_id = m["replied_to_id"]
|
58
|
+
thread_id = m["thread_id"]
|
59
|
+
body = m["body"]["plain"]
|
60
|
+
|
61
|
+
if !yamm.is_thread_starter?(id, thread_id)
|
62
|
+
whd.update_ticket(ticket_id, body)
|
63
|
+
end
|
64
|
+
}
|
65
|
+
elsif !thread_exists && !thread_starter
|
66
|
+
puts "Thread does not exist AND this is not a thread starter".red
|
67
|
+
puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".red
|
68
|
+
puts "thread_id = #{thread_id}".red
|
69
|
+
puts "messag id = #{id}".red
|
70
|
+
puts "body = #{body}\n".red
|
71
|
+
|
72
|
+
missing_thread_starter = yamm.get_thread_starter(thread_id)
|
73
|
+
|
74
|
+
id = missing_thread_starter['id']
|
75
|
+
replied_to_id = missing_thread_starter["replied_to_id"]
|
76
|
+
thread_id = missing_thread_starter["thread_id"]
|
77
|
+
body = missing_thread_starter["body"]["plain"]
|
78
|
+
|
79
|
+
# We found a missing thread starter from a reply returned by the YAPI
|
80
|
+
# Let's create a ticket from the ID we found then:
|
81
|
+
whd.create_ticket(thread_id, body)
|
82
|
+
|
83
|
+
# ... and get all the replies
|
84
|
+
ticket_id = whd.search(thread_id)
|
85
|
+
|
86
|
+
thread_replies = yamm.get_thread_replies(thread_id)
|
87
|
+
thread_replies.sort_by { |id| }
|
88
|
+
thread_replies.each { |m|
|
89
|
+
id = m['id']
|
90
|
+
replied_to_id = m["replied_to_id"]
|
91
|
+
thread_id = m["thread_id"]
|
92
|
+
body = m["body"]["plain"]
|
93
|
+
|
94
|
+
if !yamm.is_thread_starter?(id, thread_id)
|
95
|
+
whd.update_ticket(ticket_id, body)
|
96
|
+
end
|
97
|
+
}
|
98
|
+
else
|
99
|
+
puts "Thread exists AND this is a thread starter\n".red
|
100
|
+
end
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
# Now check for updated tickets, and push to Yammer
|
105
|
+
whd_tickets = whd.get
|
106
|
+
whd_tickets.sort_by { |id| }
|
107
|
+
|
108
|
+
whd_tickets.each { |t|
|
109
|
+
yamm_id = t['shortSubject']
|
110
|
+
ticket_id = t['id']
|
111
|
+
whd_id = whd.search(yamm_id)
|
112
|
+
yamm_replies = yamm.get_thread_replies(yamm_id)
|
113
|
+
whd_notes = whd.get_notes(ticket_id)
|
114
|
+
|
115
|
+
whd_notes.each { | note |
|
116
|
+
puts "Now searching for note:\n#{note}\n".red
|
117
|
+
|
118
|
+
if yamm.reply_exists?(note, yamm_replies)
|
119
|
+
puts "Ticket: #{ticket_id}\n"
|
120
|
+
puts "Thread from WHD: #{yamm_id}\n"
|
121
|
+
puts "Nothin' doin'. The note is already a comment on Yammer:\n" + "#{note}\n\n".blue
|
122
|
+
else
|
123
|
+
puts "Ticket: #{ticket_id}\n"
|
124
|
+
puts "Thread from WHD: #{yamm_id}\n"
|
125
|
+
puts "Didn't find the note in the thread. Updating it with the new reply:\n" + "#{note}\n\n".green
|
126
|
+
yamm.update_thread(yamm_id, note)
|
127
|
+
end
|
128
|
+
}
|
129
|
+
|
130
|
+
}
|
131
|
+
|
132
|
+
puts "Application run completed in #{Time.now - beginning} seconds\n"
|
data/lib/yammdesk.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "yammdesk/version"
|
2
|
+
|
3
|
+
module Yammdesk
|
4
|
+
require_relative 'yammdesk/yammer'
|
5
|
+
require 'yaml'
|
6
|
+
require 'net/http'
|
7
|
+
require 'openssl'
|
8
|
+
require 'uri'
|
9
|
+
require 'json'
|
10
|
+
require 'nokogiri'
|
11
|
+
|
12
|
+
|
13
|
+
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
14
|
+
CONFIG = YAML.load_file("config.yml") unless defined? CONFIG
|
15
|
+
end
|
data/lib/yammdesk/whd.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
module Yammdesk
|
2
|
+
|
3
|
+
class WHD
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
require 'net/http'
|
7
|
+
require 'openssl'
|
8
|
+
require 'uri'
|
9
|
+
require 'json'
|
10
|
+
require 'colorize'
|
11
|
+
|
12
|
+
CONFIG = YAML.load_file("config.yml") unless defined? CONFIG
|
13
|
+
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@url = CONFIG['whd']['url']
|
17
|
+
@token = CONFIG['whd']['tech_token']
|
18
|
+
@uri = URI(@url)
|
19
|
+
current_dir = File.dirname(__FILE__)
|
20
|
+
@post_file = JSON.parse(IO.read("#{current_dir}/../../templates/create_ticket.json"))
|
21
|
+
@update_file = JSON.parse(IO.read("#{current_dir}/../../templates/update_ticket.json"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def get
|
25
|
+
Net::HTTP.start(@uri.host, @uri.port) do |http|
|
26
|
+
response = http.get(@url + "Tickets/?list=group&apiKey=#{@token}")
|
27
|
+
# response = http.get(@url + "Tickets/?list=group", :params => {:apiKey => @token})
|
28
|
+
@data = JSON.parse(response.body)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def thread_exists?(thread_id)
|
33
|
+
url = @url + "Tickets?qualifier=(subject%3D'#{thread_id}')&apiKey=#{@token}"
|
34
|
+
uri = URI(url)
|
35
|
+
|
36
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
37
|
+
response = http.get(url)
|
38
|
+
get_data = JSON.parse(response.body)
|
39
|
+
|
40
|
+
if get_data.empty?
|
41
|
+
return false
|
42
|
+
else
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def note_exists?(ticket_id, yamm_body)
|
49
|
+
yamm_body = CGI.unescapeHTML(yamm_body)
|
50
|
+
url = @url + "TicketNotes?jobTicketId=#{ticket_id}&apiKey=#{@token}"
|
51
|
+
uri = URI(url)
|
52
|
+
|
53
|
+
Net::HTTP.start(@uri.host, @uri.port) do |http|
|
54
|
+
response = http.get(url)
|
55
|
+
get_data = JSON.parse(response.body)
|
56
|
+
|
57
|
+
if note = get_data.find { |note| CGI.unescapeHTML(note['mobileNoteText']) }
|
58
|
+
return true
|
59
|
+
else
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_notes(ticket_id)
|
66
|
+
url = @url + "TicketNotes?jobTicketId=#{ticket_id}&apiKey=#{@token}"
|
67
|
+
uri = URI(url)
|
68
|
+
|
69
|
+
Net::HTTP.start(@uri.host, @uri.port) do |http|
|
70
|
+
response = http.get(url)
|
71
|
+
get_data = JSON.parse(response.body)
|
72
|
+
|
73
|
+
@whd_entries = []
|
74
|
+
|
75
|
+
get_data.each { |h|
|
76
|
+
note = CGI.unescapeHTML(h['mobileNoteText'])
|
77
|
+
note = note.gsub('<br/> ', "\n")
|
78
|
+
note = Nokogiri::HTML.parse(note)
|
79
|
+
note = note.text
|
80
|
+
|
81
|
+
@whd_entries << note
|
82
|
+
}
|
83
|
+
|
84
|
+
return @whd_entries
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def search(thread_id)
|
89
|
+
url = @url + "Tickets?qualifier=(subject%3D'#{thread_id}')&apiKey=#{@token}"
|
90
|
+
uri = URI(url)
|
91
|
+
|
92
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
93
|
+
response = http.get(url)
|
94
|
+
#puts "DEBUG: #{response.code}, #{response.body}"
|
95
|
+
|
96
|
+
get_data = JSON.parse(response.body)
|
97
|
+
#data = (get_data[0] || {}).to_hash
|
98
|
+
data = get_data[0].to_hash
|
99
|
+
ticket_id = data["id"]
|
100
|
+
|
101
|
+
return ticket_id
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def put(options = {})
|
106
|
+
ticket_id = options[:ticket_id]
|
107
|
+
put_data = options[:put_data]
|
108
|
+
|
109
|
+
Net::HTTP.start(@uri.host, @uri.port) do |http|
|
110
|
+
response = http.request_put(@url + "Tickets/#{ticket_id}/?apiKey=#{@token}", put_data)
|
111
|
+
@data = JSON.parse(response.body)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def last_ticket
|
116
|
+
last_ticket = "10559118"
|
117
|
+
end
|
118
|
+
|
119
|
+
def update_ticket(ticket_id, message)
|
120
|
+
@update_file["ticketId"] = ticket_id
|
121
|
+
@update_file["jobticket"]["id"] = ticket_id
|
122
|
+
@update_file["noteText"] = message
|
123
|
+
post_data = @update_file.to_s
|
124
|
+
url = @url + "TechNotes?apiKey=#{@token}"
|
125
|
+
uri = URI(url)
|
126
|
+
|
127
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
128
|
+
request.body = post_data
|
129
|
+
|
130
|
+
response = Net::HTTP.new(uri.host, uri.port).start { |http|
|
131
|
+
res = http.request(request)
|
132
|
+
# puts "CODE: #{res.code} with BODY: #{res.body}\n".white
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_ticket(thread_id, body)
|
137
|
+
@post_file["subject"] = "#{thread_id}"
|
138
|
+
@post_file["detail"] = body
|
139
|
+
post_data = @post_file.to_s
|
140
|
+
|
141
|
+
url = @url + "Tickets?apiKey=#{@token}"
|
142
|
+
uri = URI(url)
|
143
|
+
|
144
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
145
|
+
request.body = post_data
|
146
|
+
|
147
|
+
response = Net::HTTP.new(uri.host, uri.port).start { |http| http.request(request) }
|
148
|
+
|
149
|
+
# DEBUG
|
150
|
+
# puts "Creating ticket for thread: ".yellow + "#{thread_id}\n" + "with message: ".yellow + "#{post_data}\n" +
|
151
|
+
# "using put URL: ".yellow + "#{url}\n\n"
|
152
|
+
# DEBUG
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Yammdesk
|
2
|
+
class Yammer
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'net/http'
|
6
|
+
require 'openssl'
|
7
|
+
require 'uri'
|
8
|
+
require 'json'
|
9
|
+
require 'nokogiri'
|
10
|
+
require_relative 'whd'
|
11
|
+
|
12
|
+
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
13
|
+
CONFIG = YAML.load_file("config.yml") unless defined? CONFIG
|
14
|
+
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@url = CONFIG['yammdesk']['url']
|
18
|
+
@token = CONFIG['yammdesk']['token']
|
19
|
+
@uri = URI(@url)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def get
|
24
|
+
Net::HTTP.start(@uri.host, @uri.port, :use_ssl => @uri.scheme == 'https') do |http|
|
25
|
+
response = http.get(@url + "/messages/in_group/872.json?oaccess_token=#{@token}")
|
26
|
+
@data = JSON.parse(response.body)
|
27
|
+
|
28
|
+
return @data['messages']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_thread_replies(thread_id)
|
33
|
+
Net::HTTP.start(@uri.host, @uri.port, :use_ssl => @uri.scheme == 'https') do |http|
|
34
|
+
response = http.get(@url + "/messages/in_thread/#{thread_id}.json?oaccess_token=#{@token}")
|
35
|
+
@data = JSON.parse(response.body)
|
36
|
+
|
37
|
+
return @data['messages']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_thread_starter(thread_id)
|
42
|
+
url = @url + "/messages/#{thread_id}.json?oaccess_token=#{@token}"
|
43
|
+
uri = URI(@url)
|
44
|
+
|
45
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
46
|
+
response = http.get(url)
|
47
|
+
@data = JSON.parse(response.body)
|
48
|
+
return @data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def reply_exists?(whd_note, yamm_replies)
|
53
|
+
if yamm_replies.find { |yamm|
|
54
|
+
body = CGI.unescapeHTML(yamm['body']['plain'])
|
55
|
+
body = body.gsub('<br/> ', "\n")
|
56
|
+
body = Nokogiri::HTML.parse(body)
|
57
|
+
body = body.text
|
58
|
+
|
59
|
+
body == whd_note
|
60
|
+
}
|
61
|
+
return true
|
62
|
+
else
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def is_thread_starter?(id, thread_id)
|
68
|
+
if id == thread_id
|
69
|
+
true
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def update_thread(thread_id, body)
|
76
|
+
body = CGI::unescapeHTML(body)
|
77
|
+
body = body.gsub('<br/> ', "\n")
|
78
|
+
body = Nokogiri::HTML.parse(body)
|
79
|
+
body = body.text
|
80
|
+
|
81
|
+
url = @url + "/messages.json&oaccess_token=#{@token}"
|
82
|
+
uri = URI(url)
|
83
|
+
|
84
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
85
|
+
https.use_ssl = true
|
86
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
87
|
+
|
88
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
89
|
+
request.set_form_data('replied_to_id' => thread_id, 'body' => body)
|
90
|
+
|
91
|
+
response = https.request(request)
|
92
|
+
puts "DIDN'T FIND:\n#{body}\n\n".white
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|