yammdesk 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|